diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c79222c3..161708bb9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,11 +43,12 @@ jobs: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }} MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }} MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }} - MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }} RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }} - RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} + RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} + MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} - name: Build Windows if: matrix.os == 'windows-latest' @@ -56,11 +57,12 @@ jobs: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }} MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }} MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }} - MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }} RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }} - RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} + RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} + MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} - name: Test Upload build env: @@ -72,6 +74,7 @@ jobs: BUILDS_URL: ${{ secrets.BUILDS_URL }} BUILD_WEBHOOK_URL: ${{ secrets.BUILD_WEBHOOK_URL }} GITHUB_ACTOR: ${{ github.actor }} + run: node scripts/upload-build.cjs - name: Create artifact diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 101221d17..afa5502cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,9 +47,12 @@ jobs: MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }} MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }} RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }} - RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} + RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} + MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} + - name: Build Windows if: matrix.os == 'windows-latest' run: yarn build:win @@ -59,9 +62,12 @@ jobs: MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }} MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }} RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }} - RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} + RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} + MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} + - name: Create artifact uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 68b8342c5..36e620fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ ludusavi/ hydra-python-rpc/ aria2/ .python-version + +# Sentry Config File +.env.sentry-build-plugin diff --git a/electron.vite.config.ts b/electron.vite.config.ts index be37cc40f..cd08b6d40 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -8,6 +8,7 @@ import { import react from "@vitejs/plugin-react"; import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; import svgr from "vite-plugin-svgr"; +import { sentryVitePlugin } from "@sentry/vite-plugin"; export default defineConfig(({ mode }) => { loadEnv(mode); @@ -44,7 +45,16 @@ export default defineConfig(({ mode }) => { "@shared": resolve("src/shared"), }, }, - plugins: [svgr(), react(), vanillaExtractPlugin()], + plugins: [ + svgr(), + react(), + vanillaExtractPlugin(), + sentryVitePlugin({ + authToken: process.env.SENTRY_AUTH_TOKEN, + org: "hydra-launcher", + project: "hydra-renderer", + }), + ], }, }; }); diff --git a/package.json b/package.json index dc11fde4e..2895f20c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.1.2", + "version": "3.1.5", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", @@ -39,19 +39,21 @@ "@primer/octicons-react": "^19.9.0", "@radix-ui/react-dropdown-menu": "^2.1.2", "@reduxjs/toolkit": "^2.2.3", + "@sentry/react": "^8.47.0", + "@sentry/vite-plugin": "^2.22.7", "@vanilla-extract/css": "^1.14.2", "@vanilla-extract/dynamic": "^2.1.2", "@vanilla-extract/recipes": "^0.5.2", "auto-launch": "^5.0.6", "axios": "^1.7.9", "better-sqlite3": "^11.7.0", - "check-disk-space": "^3.4.0", "classnames": "^2.5.1", "color": "^4.2.3", "color.js": "^1.2.0", "create-desktop-shortcuts": "^1.11.0", "date-fns": "^3.6.0", "dexie": "^4.0.10", + "diskusage": "^1.2.0", "electron-log": "^5.2.4", "electron-updater": "^6.3.9", "file-type": "^19.6.0", diff --git a/scripts/postinstall.cjs b/scripts/postinstall.cjs index 17edb025e..70768a7d3 100644 --- a/scripts/postinstall.cjs +++ b/scripts/postinstall.cjs @@ -48,11 +48,6 @@ const downloadLudusavi = async () => { }; const downloadAria2WindowsAndLinux = async () => { - if (fs.existsSync("aria2")) { - console.log("Aria2 already exists, skipping download..."); - return; - } - const file = process.platform === "win32" ? "aria2-1.37.0-win-64bit-build1.zip" @@ -111,10 +106,17 @@ const copyAria2Macos = async () => { await exec(`cp $(which aria2c) aria2/aria2c`); }; -if (process.platform == "darwin") { - copyAria2Macos(); -} else { - downloadAria2WindowsAndLinux(); -} +const copyAria2 = () => { + if (fs.existsSync("aria2")) { + console.log("Aria2 already exists, skipping download..."); + return; + } + if (process.platform == "darwin") { + copyAria2Macos(); + } else { + downloadAria2WindowsAndLinux(); + } +}; +copyAria2(); downloadLudusavi(); diff --git a/src/locales/ar/translation.json b/src/locales/ar/translation.json index a4724b21e..35a64d2de 100644 --- a/src/locales/ar/translation.json +++ b/src/locales/ar/translation.json @@ -4,399 +4,414 @@ "successfully_signed_in": "تم تسجيل الدخول بنجاح" }, "home": { - "featured": "مميّز", - "surprise_me": "فاجئني", - "no_results": "لم يتم العثور على نتائج", - "start_typing": "بدء الكتابة للبحث...", - "hot": "الأكثر رواجا الآن", - "weekly": "📅 أفضل ألعاب الأسبوع", - "achievements": "🏆 ألعاب للتغلب عليها" + "featured": "مُتَمَيِّز", + "surprise_me": "فَاجِئْنِي", + "no_results": "لَمْ يُعْثَرْ عَلَى نَتائِج", + "start_typing": "اِبْدَأْ بِالْكِتَابَةِ لِلْبَحْثِ...", + "hot": "اَلْأَكْثَرُ شُيُوعًا الْآن", + "weekly": "📅 أَفْضَلُ أَلْعَابِ الْأُسْبُوعِ", + "achievements": "🏆 أَلْعَابٌ لِلتَّغَلُّبِ عَلَيْهَا" }, "sidebar": { - "catalogue": "قائمة الألعاب", - "downloads": "التنزيلات", - "settings": "إعدادات", - "my_library": "مكتبتي", - "downloading_metadata": "{{title}} (جارٍ تنزيل البيانات الوصفية...)", - "paused": "{{title}} (متوقف مؤقتًا)", - "downloading": "{{title}} ({{percentage}} - جاري التنزيل...)", - "filter": "بحث في المكتبة", - "home": "الرئيسية", - "queued": "{{title}} (في قائمة الانتظار)", - "game_has_no_executable": "لم يتم تحديد اللعبة القابلة للتنفيذ", - "sign_in": "تسجيل الدخول", - "friends": "أصدقاء", - "need_help": "بحاجة الى مساعدة؟" + "catalogue": "الْفِهْرِسُ", + "downloads": "التَّنْزِيلَاتُ", + "settings": "الإعْدَادَاتُ", + "my_library": "مَكْتَبَتِي", + "downloading_metadata": "{{title}} (جَارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ...)", + "paused": "{{title}} (مُوْقَفٌ)", + "downloading": "{{title}} ({{percentage}} - جَارٍ التَّنْزِيلُ...)", + "filter": "تَصْفِيَةُ الْمَكْتَبَةِ", + "home": "الرَّئِيسِيَّةُ", + "queued": "{{title}} (فِي الْانْتِظَارِ)", + "game_has_no_executable": "اللُّعْبَةُ لَيْسَ لَدَيْهَا مِلَفٌّ تَنْفِيذِيٌّ مُحَدَّدٌ", + "sign_in": "تَسْجِيلُ الدُّخُولِ", + "friends": "الْأَصْدِقَاءُ", + "need_help": "هَلْ تَحْتَاجُ إِلَى مُسَاعَدَةٍ؟" }, "header": { - "search": "ابحث عن الألعاب", - "home": "الرئيسية", - "catalogue": "قائمة الألعاب", - "downloads": "التنزيلات", - "search_results": "نتائج البحث", - "settings": "إعدادات", - "version_available_install": "إصدار {{version}} متاح. ", - "version_available_download": "إصدار {{version}} متاح. " + "search": "بَحْثُ الْأَلْعَابِ", + "home": "الرَّئِيسِيَّةُ", + "catalogue": "الْفِهْرِسُ", + "downloads": "التَّنْزِيلَاتُ", + "search_results": "نَتائِجُ الْبَحْثِ", + "settings": "الإعْدَادَاتُ", + "version_available_install": "الْإِصْدَارُ {{version}} مَتَوَفِّرٌ. انْقُرْ هُنَا لِإِعَادَةِ التَّشْغِيلِ وَالتَّثْبِيتِ.", + "version_available_download": "الْإِصْدَارُ {{version}} مَتَوَفِّرٌ. انْقُرْ هُنَا لِلتَّنْزِيلِ." }, "bottom_panel": { - "no_downloads_in_progress": "لا توجد تنزيلات قيد التقدم", - "downloading_metadata": "جارٍ التنزيل {{title}} البيانات الوصفية...", - "downloading": "جارٍ التنزيل {{title}}… ({{percentage}} مكتملة) - الانتهاء {{eta}} - {{speed}}", - "calculating_eta": "جارٍ التنزيل {{title}}… ({{percentage}} مكتمل) - حساب الوقت المتبقي...", - "checking_files": "التحقق {{title}} ملفات…({{percentage}} مكتمل)" + "no_downloads_in_progress": "لَا تَوْجَدُ تَنْزِيلَاتٌ جَارِيَةٌ", + "downloading_metadata": "جَارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ لِـ {{title}}...", + "downloading": "جَارٍ تَنْزِيلُ {{title}}... ({{percentage}} مَكْتُومٌ) - الِاكْتِمَالُ {{eta}} - {{speed}}", + "calculating_eta": "جَارٍ تَنْزِيلُ {{title}}... ({{percentage}} مَكْتُومٌ) - جَارٍ حِسَابُ الْوَقْتِ الْمُتَبَقِّي...", + "checking_files": "جَارٍ التَّحَقُّقُ مِنْ مَلَفَّاتِ {{title}}... ({{percentage}} مَكْتُومٌ)" }, "catalogue": { - "next_page": "الصفحة التالية", - "previous_page": "الصفحة السابقة" + "search": "تَصْفِيَةٌ...", + "developers": "الْمُطَوِّرُونَ", + "genres": "الْأَنْوَاعُ", + "tags": "الْعَلَامَاتُ", + "publishers": "النَّاشِرُونَ", + "download_sources": "مَصَادِرُ التَّنْزِيلِ", + "result_count": "{{resultCount}} نَتائِجُ", + "filter_count": "{{filterCount}} مَتَوَفِّرٌ", + "clear_filters": "مَسْحُ {{filterCount}} الْمُخْتَارَةِ" }, "game_details": { - "open_download_options": "افتح خيارات التنزيل", - "download_options_zero": "{{count}} خيارات التنزيل", - "updated_at": "تم التحديث {{updated_at}}", - "install": "ثَبَّتَ", - "resume": "استئناف", - "pause": "إيقاف", - "cancel": "إلغاء", - "remove": "إزالة", - "space_left_on_disk": "{{space}} متبقية على القرص", - "eta": "الوقت المتبقي {{eta}}", - "calculating_eta": "جارٍ حساب الوقت المتبقي…", - "downloading_metadata": "جارٍ تنزيل البيانات الوصفية…", - "filter": "إعادة حزم التصفية", - "requirements": "متطلبات النظام", - "minimum": "الحد الأدنى", - "recommended": "مُستَحسَن", - "paused": "متوقف مؤقتًا", - "release_date": "صدر بتاريخ {{date}}", - "publisher": "نشرت من قبل {{publisher}}", - "hours": "ساعات", - "minutes": "دقائق", - "amount_hours": "{{amount}} ساعات", - "amount_minutes": "{{amount}} دقائق", - "accuracy": "{{accuracy}}٪ دقة", - "add_to_library": "أضف إلى المكتبة", - "remove_from_library": "إزالة من المكتبة", - "no_downloads": "لا التنزيلات المتاحة", - "play_time": "تم اللعب لمدة {{amount}}", - "last_time_played": "لعبت آخر مرة {{period}}", - "not_played_yet": "أنت لم تلعب {{title}} حتى الآن", - "next_suggestion": "الاقتراح التالي", - "play": "لعب", - "deleting": "جارٍ حذف المثبت…", - "close": "إغلاق", - "playing_now": "قيداللعب الآن", - "change": "تغيير", - "repacks_modal_description": "اختر الحزمة التي تريد تنزيلها", - "select_folder_hint": "لتغيير المجلد الافتراضي، انتقل إلى <0>إعدادات", - "download_now": "قم بالتنزيل الآن", - "no_shop_details": "لا يمكن استرداد تفاصيل المتجر.", - "download_options": "خيارات التنزيل", - "download_path": "مسار التحميل", - "previous_screenshot": "لقطة الشاشة السابقة", - "next_screenshot": "لقطة الشاشة التالية", - "screenshot": "لقطة الشاشة {{number}}", - "open_screenshot": "فتح لقطة الشاشة {{number}}", - "download_settings": "تحميل الإعدادات", - "downloader": "أداة التنزيل", - "select_executable": "يختار", - "no_executable_selected": "لم يتم تحديد أي ملف قابل للتنفيذ", - "open_folder": "افتح المجلد", - "open_download_location": "انظر الملفات التي تم تنزيلها", - "create_shortcut": "إنشاء اختصار سطح المكتب", - "clear": "واضح", - "remove_files": "إزالة الملفات", - "remove_from_library_title": "هل أنت متأكد؟", - "remove_from_library_description": "سيتم إزالة هذا {{game}} من مكتبتك", - "options": "خيارات", - "executable_section_title": "قابل للتنفيذ", - "executable_section_description": "مسار الملف الذي سيتم تنفيذه عند النقر فوق \"تشغيل\".", - "downloads_secion_title": "التنزيلات", - "downloads_section_description": "تحقق من التحديثات أو الإصدارات الأخرى من هذه اللعبة", - "danger_zone_section_title": "منطقة الخطر", - "danger_zone_section_description": "قم بإزالة هذه اللعبة من مكتبتك أو الملفات التي تم تنزيلها بواسطة Hydra", - "download_in_progress": "التنزيل قيد التقدم", - "download_paused": "تم إيقاف التنزيل مؤقتًا", - "last_downloaded_option": "آخر خيار تم تنزيله", - "create_shortcut_success": "تم إنشاء الاختصار بنجاح", - "create_shortcut_error": "حدث خطأ أثناء إنشاء الاختصار", - "nsfw_content_title": "تحتوي هذه اللعبة على محتوى غير مناسب", - "nsfw_content_description": "{{title}} يحتوي على محتوى قد لا يكون مناسبًا لجميع الأعمار. ", - "allow_nsfw_content": "اسمح", - "refuse_nsfw_content": "عُد", - "stats": "احصائيات", - "download_count": "التنزيلات", - "player_count": "اللاعبين النشطين", - "download_error": "خيار التنزيل هذا غير متوفر", - "download": "تحميل", - "executable_path_in_use": "قابل للتنفيذ قيد الاستخدام بالفعل بواسطة \"{{game}}\"", - "warning": "تحذير:", - "hydra_needs_to_remain_open": "لإجراء هذا التنزيل، يجب أن يظل Hydra مفتوحًا حتى اكتماله. ", - "achievements": "الإنجازات", - "achievements_count": "الإنجازات {{unlockedCount}}/{{achievementsCount}}", - "cloud_save": "حفظ السحابة", - "cloud_save_description": "احفظ تقدمك في السحابة واستمر في اللعب على أي جهاز", - "backups": "النسخ الاحتياطية", - "install_backup": "ثَبَّتَ", - "delete_backup": "يمسح", - "create_backup": "نسخة احتياطية جديدة", - "last_backup_date": "آخر نسخة احتياطية قيد التشغيل {{date}}", - "no_backup_preview": "لم يتم العثور على ألعاب محفوظة لهذا العنوان", - "restoring_backup": "استعادة النسخة الاحتياطية ({{progress}} مكتمل)…", - "uploading_backup": "جارٍ تحميل النسخة الاحتياطية…", - "no_backups": "لم تقم بإنشاء أي نسخ احتياطية لهذه اللعبة حتى الآن", - "backup_uploaded": "تم تحميل النسخة الاحتياطية", - "backup_deleted": "تم حذف النسخة الاحتياطية", - "backup_restored": "تمت استعادة النسخة الاحتياطية", - "see_all_achievements": "شاهد جميع الإنجازات", - "sign_in_to_see_achievements": "قم بتسجيل الدخول لرؤية الإنجازات", - "mapping_method_automatic": "تلقائي", - "mapping_method_manual": "يدوي", - "mapping_method_label": "طريقة رسم الخرائط", - "files_automatically_mapped": "تم تعيين الملفات تلقائيًا", - "no_backups_created": "لم يتم إنشاء نسخ احتياطية لهذه اللعبة", - "manage_files": "إدارة الملفات", - "loading_save_preview": "جارٍ البحث عن حفظ الألعاب...", - "wine_prefix": "بادئة النبيذ", - "wine_prefix_description": "بادئة Wine المستخدمة لتشغيل هذه اللعبة", - "no_download_option_info": "لا توجد معلومات متاحة", - "backup_deletion_failed": "فشل في حذف النسخة الاحتياطية", - "max_number_of_artifacts_reached": "تم الوصول إلى الحد الأقصى لعدد النسخ الاحتياطية لهذه اللعبة", - "achievements_not_sync": "لا تتم مزامنة إنجازاتك", - "manage_files_description": "إدارة الملفات التي سيتم نسخها احتياطيًا واستعادتها", - "select_folder": "حدد المجلد", - "backup_from": "نسخة احتياطية من {{date}}", - "custom_backup_location_set": "تعيين موقع النسخ الاحتياطي المخصص", - "no_directory_selected": "لم يتم تحديد أي دليل", - "download_options_one": "{{count}} خيار التنزيل", - "download_options_two": "{{count}} خيارات التنزيل", - "download_options_few": "{{count}} خيارات التنزيل", - "download_options_many": "{{count}} خيارات التنزيل", - "download_options_other": "{{count}} خيارات التنزيل" + "open_download_options": "فَتْحُ خِيَارَاتِ التَّنْزِيلِ", + "download_options_zero": "لَا تَوْجَدُ خِيَارَاتُ تَنْزِيلٍ", + "download_options_one": "{{count}} خِيَارُ تَنْزِيلٍ", + "download_options_other": "{{count}} خِيَارَاتُ تَنْزِيلٍ", + "updated_at": "تَمَّ التَّحْدِيثُ فِي {{updated_at}}", + "install": "تَثْبِيتٌ", + "resume": "اسْتِئْنَافٌ", + "pause": "إِيقَافٌ", + "cancel": "إِلْغَاءٌ", + "remove": "إِزَالَةٌ", + "space_left_on_disk": "{{space}} مُتَبَقٍّ عَلَى الْقُرْصِ", + "eta": "الِاكْتِمَالُ {{eta}}", + "calculating_eta": "جَارٍ حِسَابُ الْوَقْتِ الْمُتَبَقِّي...", + "downloading_metadata": "جَارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ...", + "filter": "تَصْفِيَةُ الْإِصْدَارَاتِ الْمُعَادِ تَغْلِيفُهَا", + "requirements": "مُتَطَلَّبَاتُ النِّظَامِ", + "minimum": "الْأَدْنَى", + "recommended": "الْمُوَصَّى بِهِ", + "paused": "مُوْقَفٌ", + "release_date": "تَمَّ الْإِصْدَارُ فِي {{date}}", + "publisher": "نُشِرَ بِوَاسِطَةِ {{publisher}}", + "hours": "سَاعَاتٌ", + "minutes": "دَقَائِقُ", + "amount_hours": "{{amount}} سَاعَاتٌ", + "amount_minutes": "{{amount}} دَقَائِقُ", + "accuracy": "دِقَّةٌ {{accuracy}}%", + "add_to_library": "إِضَافَةٌ إِلَى الْمَكْتَبَةِ", + "remove_from_library": "إِزَالَةٌ مِنَ الْمَكْتَبَةِ", + "no_downloads": "لَا تَوْجَدُ تَنْزِيلَاتٌ مَتَوَفِّرَةٌ", + "play_time": "لُعِبَ لِمُدَّةِ {{amount}}", + "last_time_played": "آخِرُ مَرَّةٍ لُعِبَتْ {{period}}", + "not_played_yet": "لَمْ تَلْعَبْ {{title}} بَعْدُ", + "next_suggestion": "الِاقْتِرَاحُ التَّالِي", + "play": "لَعِبٌ", + "deleting": "جَارٍ حَذْفُ الْمُثَبِّتِ...", + "close": "إِغْلَاقٌ", + "playing_now": "جَارِي اللَّعِبُ الْآن", + "change": "تَغْيِيرٌ", + "repacks_modal_description": "اخْتَرِ الْإِصْدَارَ الْمُعَادَ تَغْلِيفُهُ الَّذِي تُرِيدُ تَنْزِيلَهُ", + "select_folder_hint": "لِتَغْيِيرِ الْمَجَلَّدِ الافْتِرَاضِيِّ، اذْهَبْ إِلَى <0>الإعْدَادَاتِ", + "download_now": "تَنْزِيلٌ الْآن", + "no_shop_details": "لَمْ يَتَمَكَّنْ مِنْ اسْتِرْدَادِ تَفَاصِيلِ الْمَتْجَرِ.", + "download_options": "خِيَارَاتُ التَّنْزِيلِ", + "download_path": "مَسَارُ التَّنْزِيلِ", + "previous_screenshot": "لَقْطَةُ الشَّاشَةِ السَّابِقَةُ", + "next_screenshot": "لَقْطَةُ الشَّاشَةِ التَّالِيَةُ", + "screenshot": "لَقْطَةُ الشَّاشَةِ {{number}}", + "open_screenshot": "فَتْحُ لَقْطَةِ الشَّاشَةِ {{number}}", + "download_settings": "إعْدَادَاتُ التَّنْزِيلِ", + "downloader": "الْمُنَزِّلُ", + "select_executable": "تَحْدِيدٌ", + "no_executable_selected": "لَمْ يُحَدَّدْ مِلَفٌّ تَنْفِيذِيٌّ", + "open_folder": "فَتْحُ الْمَجَلَّدِ", + "open_download_location": "مُشَاهَدَةُ الْمَلَفَّاتِ الْمُنَزَّلَةِ", + "create_shortcut": "إِنْشَاءُ طَرِيقٍ مُخْتَصَرٍ عَلَى سَطْحِ الْمَكْتَبِ", + "clear": "مَسْحٌ", + "remove_files": "إِزَالَةُ الْمَلَفَّاتِ", + "remove_from_library_title": "هَلْ أَنْتَ مُتَأَكِّدٌ؟", + "remove_from_library_description": "سَيُؤَدِّي هَذَا إِلَى إِزَالَةِ {{game}} مِنْ مَكْتَبَتِكَ", + "options": "خِيَارَاتٌ", + "executable_section_title": "الْمِلَفُّ التَّنْفِيذِيُّ", + "executable_section_description": "مَسَارُ الْمِلَفِّ الَّذِي سَيَتِمُّ تَنْفِيذُهُ عِنْدَ النَّقْرِ عَلَى \"لَعِبٌ\"", + "downloads_secion_title": "التَّنْزِيلَاتُ", + "downloads_section_description": "تَحَقَّقْ مِنَ التَّحْدِيثَاتِ أَوِ الْإِصْدَارَاتِ الْأُخْرَى لِهَذِهِ اللُّعْبَةِ", + "danger_zone_section_title": "مِنْطَقَةُ الْخَطَرِ", + "danger_zone_section_description": "إِزَالَةُ هَذِهِ اللُّعْبَةِ مِنْ مَكْتَبَتِكَ أَوِ الْمَلَفَّاتِ الْمُنَزَّلَةِ بِوَاسِطَةِ Hydra", + "download_in_progress": "جَارٍ التَّنْزِيلُ", + "download_paused": "التَّنْزِيلُ مُوْقَفٌ", + "last_downloaded_option": "خِيَارُ التَّنْزِيلِ الْأَخِيرُ", + "create_shortcut_success": "تَمَّ إِنْشَاءُ الطَّرِيقِ الْمُخْتَصَرِ بِنَجَاحٍ", + "create_shortcut_error": "خَطَأٌ فِي إِنْشَاءِ الطَّرِيقِ الْمُخْتَصَرِ", + "nsfw_content_title": "هَذِهِ اللُّعْبَةُ تَحْتَوِي عَلَى مُحْتَوًى غَيْرِ لَائِقٍ", + "nsfw_content_description": "{{title}} تَحْتَوِي عَلَى مُحْتَوًى قَدْ لَا يَكُونُ مُنَاسِبًا لِجَمِيعِ الْأَعْمَارِ. هَلْ أَنْتَ مُتَأَكِّدٌ مِنْ أَنَّكَ تُرِيدُ الْمُتَابَعَةَ؟", + "allow_nsfw_content": "الْمُتَابَعَةُ", + "refuse_nsfw_content": "الرُّجُوعُ", + "stats": "الإحْصَائِيَّاتُ", + "download_count": "التَّنْزِيلَاتُ", + "player_count": "اللَّاعِبُونَ النَّشِطُونَ", + "download_error": "هَذَا خِيَارُ التَّنْزِيلِ غَيْرُ مَتَوَفِّرٍ", + "download": "تَنْزِيلٌ", + "executable_path_in_use": "الْمِلَفُّ التَّنْفِيذِيُّ مُسْتَخْدَمٌ بِوَاسِطَةِ \"{{game}}\"", + "warning": "تَنْبِيهٌ:", + "hydra_needs_to_remain_open": "لِهَذَا التَّنْزِيلِ، يَجِبُ أَنْ يَبْقَى Hydra مَفْتُوحًا حَتَّى يَتِمَّ الِاكْتِمَالُ. إِذَا أُغْلِقَ Hydra قَبْلَ الِاكْتِمَالِ، سَتَفْقِدُ تَقَدُّمَكَ.", + "achievements": "الإِنْجَازَاتُ", + "achievements_count": "الإِنْجَازَاتُ {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "حِفْظٌ سَحَابِيٌّ", + "cloud_save_description": "احْفَظْ تَقَدُّمَكَ فِي السَّحَابَةِ وَاسْتَمِرَّ فِي اللَّعِبِ عَلَى أَيِّ جِهَازٍ", + "backups": "الْنُسَخُ الِاحْتِيَاطِيَّةُ", + "install_backup": "تَثْبِيتٌ", + "delete_backup": "حَذْفٌ", + "create_backup": "نُسْخَةٌ احْتِيَاطِيَّةٌ جَدِيدَةٌ", + "last_backup_date": "آخِرُ نُسْخَةٍ احْتِيَاطِيَّةٍ فِي {{date}}", + "no_backup_preview": "لَمْ يُعْثَرْ عَلَى أَيِّ أَلْعَابٍ مَحْفُوظَةٍ لِهَذَا الْعُنْوَانِ", + "restoring_backup": "جَارٍ اسْتِعَادَةُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ ({{progress}} مَكْتُومٌ)...", + "uploading_backup": "جَارٍ رَفْعُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ...", + "no_backups": "لَمْ تَقُمْ بِإِنْشَاءِ أَيِّ نُسَخٍ احْتِيَاطِيَّةٍ لِهَذِهِ اللُّعْبَةِ بَعْدُ", + "backup_uploaded": "تَمَّ رَفْعُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ", + "backup_deleted": "تَمَّ حَذْفُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ", + "backup_restored": "تَمَّ اسْتِعَادَةُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ", + "see_all_achievements": "عَرْضُ جَمِيعِ الإِنْجَازَاتِ", + "sign_in_to_see_achievements": "سَجِّلِ الدُّخُولَ لِعَرْضِ الإِنْجَازَاتِ", + "mapping_method_automatic": "آلِيٌّ", + "mapping_method_manual": "يَدَوِيٌّ", + "mapping_method_label": "طَرِيقَةُ التَّحْدِيدِ", + "files_automatically_mapped": "تَمَّ تَحْدِيدُ الْمَلَفَّاتِ تِلْقَائِيًّا", + "no_backups_created": "لَمْ تُنْشَأْ أَيُّ نُسَخٍ احْتِيَاطِيَّةٍ لِهَذِهِ اللُّعْبَةِ", + "manage_files": "إِدَارَةُ الْمَلَفَّاتِ", + "loading_save_preview": "جَارٍ الْبَحْثُ عَنْ أَلْعَابٍ مَحْفُوظَةٍ...", + "wine_prefix": "بَادِئَةُ Wine", + "wine_prefix_description": "بَادِئَةُ Wine الْمُسْتَخْدَمَةُ لِتَشْغِيلِ هَذِهِ اللُّعْبَةِ", + "launch_options": "خِيَارَاتُ الْإِطْلَاقِ", + "launch_options_description": "يُمْكِنُ لِلْمُسْتَخْدِمِينَ الْمُتَقَدِّمِينَ إِدْخَالُ تَعْدِيلَاتٍ عَلَى خِيَارَاتِ الْإِطْلَاقِ", + "launch_options_placeholder": "لَمْ يُحَدَّدْ أَيُّ مُعَامِلٍ", + "no_download_option_info": "لَا تَوْجَدُ مَعْلُومَاتٌ مَتَوَفِّرَةٌ", + "backup_deletion_failed": "فَشَلَ فِي حَذْفِ النُّسْخَةِ الِاحْتِيَاطِيَّةِ", + "max_number_of_artifacts_reached": "تَمَّ بَلُوغُ الْعَدَدِ الْأَقْصَى لِلنُّسَخِ الِاحْتِيَاطِيَّةِ لِهَذِهِ اللُّعْبَةِ", + "achievements_not_sync": "تَعَرَّفْ عَلَى كَيْفِيَّةِ مَزْجِ إِنْجَازَاتِكَ", + "manage_files_description": "إِدَارَةُ الْمَلَفَّاتِ الَّتِي سَيَتِمُّ نَسْخُهَا احْتِيَاطِيًّا وَاسْتِعَادَتُهَا", + "select_folder": "تَحْدِيدُ الْمَجَلَّدِ", + "backup_from": "نُسْخَةٌ احْتِيَاطِيَّةٌ مِنْ {{date}}", + "custom_backup_location_set": "تَمَّ تَحْدِيدُ مَوْقِعِ النُّسْخَةِ الِاحْتِيَاطِيَّةِ الْمُخَصَّصِ", + "no_directory_selected": "لَمْ يُحَدَّدْ أَيُّ دَلِيلٍ" }, "activation": { - "title": "تفعيل Hydra", - "installation_id": "معرف التثبيت:", - "enter_activation_code": "أدخل رمز التفعيل الخاص بك", - "message": "إذا كنت لا تعرف أين تطلب هذا، فلا ينبغي أن يكون لديك هذا.", - "activate": "فعل", - "loading": "تحميل…" + "title": "تَفْعِيلُ Hydra", + "installation_id": "مُعَرِّفُ التَّثْبِيتِ:", + "enter_activation_code": "أَدْخِلْ رَمْزَ التَّفْعِيلِ", + "message": "إِذَا كُنْتَ لَا تَعْرِفُ أَيْنَ تَطْلُبُ هَذَا، فَلا يَجِبُ أَنْ تَكُونَ لَدَيْكَ.", + "activate": "تَفْعِيلٌ", + "loading": "جَارٍ التَّحْمِيلُ..." }, "downloads": { - "resume": "استئناف", - "pause": "إيقاف مؤقت", - "eta": "الوقت المتبقي {{eta}}", - "paused": "متوقف مؤقتًا", - "verifying": "جارٍ التحقق…", - "completed": "مكتمل", - "removed": "لم يتم تحميلها", - "cancel": "إلغاء", - "filter": "تصفية الألعاب التي تم تنزيلها", - "remove": "إزالة", - "downloading_metadata": "جارٍ تنزيل البيانات الوصفية…", - "deleting": "جارٍ حذف المثبت…", - "delete": "إزالة المثبت", - "delete_modal_title": "هل أنت متأكد؟", - "delete_modal_description": "سيؤدي هذا إلى إزالة كافة ملفات التثبيت من جهاز الكمبيوتر الخاص بك", - "install": "ثَبَّتَ", - "download_in_progress": "في تَقَدم", - "queued_downloads": "التنزيلات في قائمة الانتظار", - "downloads_completed": "مكتمل", - "queued": "في قائمة الانتظار", - "no_downloads_title": "هذا فارغ", - "no_downloads_description": "لم تقم بتنزيل أي شيء باستخدام Hydra بعد، ولكن لم يفت الأوان بعد للبدء.", - "checking_files": "جارٍ فحص الملفات…" + "resume": "اسْتِئْنَافٌ", + "pause": "إِيقَافٌ", + "eta": "الِاكْتِمَالُ {{eta}}", + "paused": "مُوْقَفٌ", + "verifying": "جَارٍ التَّحَقُّقُ...", + "completed": "مَكْتُومٌ", + "removed": "لَمْ يُنَزَّلْ", + "cancel": "إِلْغَاءٌ", + "filter": "تَصْفِيَةُ الْأَلْعَابِ الْمُنَزَّلَةِ", + "remove": "إِزَالَةٌ", + "downloading_metadata": "جَارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ...", + "deleting": "جَارٍ حَذْفُ الْمُثَبِّتِ...", + "delete": "حَذْفُ الْمُثَبِّتِ", + "delete_modal_title": "هَلْ أَنْتَ مُتَأَكِّدٌ؟", + "delete_modal_description": "سَيُؤَدِّي هَذَا إِلَى إِزَالَةِ جَمِيعِ مَلَفَّاتِ التَّثْبِيتِ مِنْ حَاسُوبِكَ", + "install": "تَثْبِيتٌ", + "download_in_progress": "جَارٍ التَّنْفِيذُ", + "queued_downloads": "التَّنْزِيلَاتُ فِي الْانْتِظَارِ", + "downloads_completed": "مَكْتُومٌ", + "queued": "فِي الْانْتِظَارِ", + "no_downloads_title": "فَرَاغٌ تَامٌ", + "no_downloads_description": "لَمْ تَقُمْ بِتَنْزِيلِ أَيِّ شَيْءٍ بِاسْتِخْدَامِ Hydra بَعْدُ، لَكِنَّهُ لَيْسَ مُتَأَخِّرًا لِلْبَدْءِ.", + "checking_files": "جَارٍ التَّحَقُّقُ مِنَ الْمَلَفَّاتِ...", + "seeding": "الْبَذْرُ", + "stop_seeding": "إِيقَافُ الْبَذْرِ", + "resume_seeding": "اسْتِئْنَافُ الْبَذْرِ", + "options": "إِدَارَةٌ" }, "settings": { - "downloads_path": "مسار التنزيلات", - "change": "تحديث", - "notifications": "إشعارات", - "enable_download_notifications": "عند اكتمال التنزيل", - "enable_repack_list_notifications": "عند إضافة حزمة جديدة", - "real_debrid_api_token_label": "رمز Real-Debrid API", - "quit_app_instead_hiding": "لا تخفي Hydra عند الإغلاق", - "launch_with_system": "قم بتشغيل Hydra عند بدء تشغيل النظام", - "general": "عام", - "behavior": "سلوك", - "download_sources": "تحميل المصادر", - "language": "لغة", - "real_debrid_api_token": "رمز API", - "enable_real_debrid": "تمكين ريال ديبريد", - "real_debrid_description": "Real-Debrid هو برنامج تنزيل غير مقيد يسمح لك بتنزيل الملفات بسرعة، ولا يقتصر ذلك إلا على سرعة الإنترنت لديك.", - "real_debrid_invalid_token": "رمز API غير صالح", - "real_debrid_api_token_hint": "يمكنك الحصول على رمز API الخاص بك <0>هنا", - "real_debrid_free_account_error": "الحساب \"{{username}}\" هو حساب مجاني. يرجى الاشتراك في Real-Debrid", - "real_debrid_linked_message": "حساب \"{{username}}\"مرتبط", - "save_changes": "حفظ التغييرات", - "changes_saved": "تم حفظ التغييرات بنجاح", - "download_sources_description": "ستقوم Hydra بجلب روابط التنزيل من هذه المصادر. ", - "validate_download_source": "التحقق من صحة", - "remove_download_source": "إزالة", - "add_download_source": "أضف المصدر", - "download_count_zero": "{{countFormatted}} خيارات التنزيل", - "download_source_url": "تنزيل عنوان URL المصدر", - "add_download_source_description": "أدخل عنوان URL لملف .json", - "download_source_up_to_date": "محدث", - "download_source_errored": "خطأ", - "sync_download_sources": "مصادر المزامنة", - "removed_download_source": "تمت إزالة مصدر التنزيل", - "added_download_source": "تمت إضافة مصدر التنزيل", - "download_sources_synced": "تتم مزامنة جميع مصادر التنزيل", - "insert_valid_json_url": "أدخل عنوان URL صالحًا لـ JSON", - "found_download_option_zero": "وجد {{countFormatted}} خيارات التنزيل", - "import": "يستورد", - "public": "عام", - "private": "خاص", - "friends_only": "الأصدقاء فقط", - "privacy": "خصوصية", - "profile_visibility": "رؤية الملف الشخصي", - "profile_visibility_description": "اختر من يمكنه رؤية ملفك الشخصي ومكتبتك", - "required_field": "هذه الخانة مطلوبه", - "source_already_exists": "تمت إضافة هذا المصدر بالفعل", - "must_be_valid_url": "يجب أن يكون المصدر عنوان URL صالحًا", - "blocked_users": "المستخدمين المحظورين", - "user_unblocked": "تم إلغاء حظر المستخدم", - "enable_achievement_notifications": "عندما يتم فتح الإنجاز", - "launch_minimized": "تم تصغير إطلاق Hydra", - "disable_nsfw_alert": "تعطيل تنبيه NSFW", - "show_hidden_achievement_description": "إظهار وصف الإنجازات المخفية قبل فتحها", - "download_count_one": "{{countFormatted}} خيار التنزيل", - "download_count_two": "{{countFormatted}} خيارات التنزيل", - "download_count_few": "{{countFormatted}} خيارات التنزيل", - "download_count_many": "{{countFormatted}} خيارات التنزيل", - "download_count_other": "{{countFormatted}} خيارات التنزيل", - "found_download_option_one": "وجد {{countFormatted}} خيار التنزيل", - "found_download_option_two": "وجد {{countFormatted}} خيارات التنزيل", - "found_download_option_few": "وجد {{countFormatted}} خيارات التنزيل", - "found_download_option_many": "وجد {{countFormatted}} خيارات التنزيل", - "found_download_option_other": "وجد {{countFormatted}} خيارات التنزيل" + "downloads_path": "مَسَارُ التَّنْزِيلَاتِ", + "change": "تَحْدِيثٌ", + "notifications": "الإِشْعَارَاتُ", + "enable_download_notifications": "عِنْدَ اكْتِمَالِ التَّنْزِيلِ", + "enable_repack_list_notifications": "عِنْدَ إِضَافَةِ إِصْدَارٍ مُعَادٍ تَغْلِيفِهِ جَدِيدٍ", + "real_debrid_api_token_label": "رَمْزُ واجهة برمجة التطبيقات Real-Debrid", + "quit_app_instead_hiding": "لا تُخْفِ Hydra عِنْدَ الإِغْلَاقِ", + "launch_with_system": "تَشْغِيلُ Hydra عِنْدَ بَدْءِ النِّظَامِ", + "general": "عَامٌ", + "behavior": "سُلُوكٌ", + "download_sources": "مَصَادِرُ التَّنْزِيلِ", + "language": "اللُّغَةُ", + "real_debrid_api_token": "رَمْزُ واجهة برمجة التطبيقات", + "enable_real_debrid": "تَمْكِينُ Real-Debrid", + "real_debrid_description": "Real-Debrid هُوَ مُنَزِّلٌ غَيْرُ مَقْيُودٍ يَتِيحُ لَكَ تَنْزِيلَ الْمَلَفَّاتِ بِسُرْعَةٍ، مَحْدُودٌ فَقَطْ بِسُرْعَةِ الْإِنْتَرْنِتِ لَدَيْكَ.", + "real_debrid_invalid_token": "رَمْزُ واجهة برمجة التطبيقات غَيْرُ صَالِحٍ", + "real_debrid_api_token_hint": "يُمْكِنُكَ الْحُصُولُ عَلَى رَمْزِ واجهة برمجة التطبيقات <0>هُنَا", + "real_debrid_free_account_error": "الْحِسَابُ \"{{username}}\" هُوَ حِسَابٌ مَجَّانِيٌّ. يَرْجَى الِاشْتِرَاكُ فِي Real-Debrid", + "real_debrid_linked_message": "تَمَّ رَبْطُ الْحِسَابِ \"{{username}}\"", + "save_changes": "حِفْظُ التَّغْيِيرَاتِ", + "changes_saved": "تَمَّ حِفْظُ التَّغْيِيرَاتِ بِنَجَاحٍ", + "download_sources_description": "سَتَقُومُ Hydra بِجَلْبِ رَوَابِطِ التَّنْزِيلِ مِنْ هَذِهِ الْمَصَادِرِ. يَجِبُ أَنْ يَكُونَ عُنْوَانُ URL لِلْمَصْدَرِ رَابِطًا مُبَاشِرًا إِلَى مِلَفٍّ .json يَحْتَوِي عَلَى رَوَابِطِ التَّنْزِيلِ.", + "validate_download_source": "تَصْدِيقٌ", + "remove_download_source": "إِزَالَةٌ", + "add_download_source": "إِضَافَةُ مَصْدَرٍ", + "download_count_zero": "لَا تَوْجَدُ خِيَارَاتُ تَنْزِيلٍ", + "download_count_one": "{{countFormatted}} خِيَارُ تَنْزِيلٍ", + "download_count_other": "{{countFormatted}} خِيَارَاتُ تَنْزِيلٍ", + "download_source_url": "عُنْوَانُ مَصْدَرِ التَّنْزِيلِ", + "add_download_source_description": "أَدْخِلْ عُنْوَانَ URL لِمِلَفٍّ .json", + "download_source_up_to_date": "مُحَدَّثٌ", + "download_source_errored": "خَطَأٌ", + "sync_download_sources": "مَزْجُ الْمَصَادِرِ", + "removed_download_source": "تَمَّ إِزَالَةُ مَصْدَرِ التَّنْزِيلِ", + "added_download_source": "تَمَّتْ إِضَافَةُ مَصْدَرِ التَّنْزِيلِ", + "download_sources_synced": "تَمَّ مَزْجُ جَمِيعِ مَصَادِرِ التَّنْزِيلِ", + "insert_valid_json_url": "أَدْخِلْ عُنْوَانَ JSON صَالِحًا", + "found_download_option_zero": "لَمْ يُعْثَرْ عَلَى خِيَارِ تَنْزِيلٍ", + "found_download_option_one": "عُثِرَ عَلَى {{countFormatted}} خِيَارِ تَنْزِيلٍ", + "found_download_option_other": "عُثِرَ عَلَى {{countFormatted}} خِيَارَاتِ تَنْزِيلٍ", + "import": "اسْتِيرَادٌ", + "public": "عَامٌ", + "private": "خَاصٌ", + "friends_only": "الْأَصْدِقَاءُ فَقَطْ", + "privacy": "الْخُصُوصِيَّةُ", + "profile_visibility": "رُؤْيَةُ الْمَلَفِّ الشَّخْصِيِّ", + "profile_visibility_description": "اخْتَرْ مَنْ يُمْكِنُهُ رُؤْيَةُ مَلَفِّكَ الشَّخْصِيِّ وَمَكْتَبَتِكَ", + "required_field": "هَذَا الْحَقْلُ مَطْلُوبٌ", + "source_already_exists": "تَمَّتْ إِضَافَةُ هَذَا الْمَصْدَرِ مِنْ قَبْلُ", + "must_be_valid_url": "يَجِبُ أَنْ يَكُونَ الْمَصْدَرُ عُنْوَانَ URL صَالِحًا", + "blocked_users": "الْمُسْتَخْدِمُونَ الْمَحْظُورُونَ", + "user_unblocked": "تَمَّ إِزَالَةُ حَظْرِ الْمُسْتَخْدِمِ", + "enable_achievement_notifications": "عِنْدَ فَتْحِ إِنْجَازٍ", + "launch_minimized": "تَشْغِيلُ Hydra مُصَغَّرًا", + "disable_nsfw_alert": "تَعْطِيلُ تَنْبِيهِ الْمُحْتَوَى غَيْرِ اللَّائِقِ", + "seed_after_download_complete": "الْبَذْرُ بَعْدَ اكْتِمَالِ التَّنْزِيلِ", + "show_hidden_achievement_description": "إِظْهَارُ وَصْفِ الإِنْجَازَاتِ الْمَخْفِيَّةِ قَبْلَ فَتْحِهَا" }, "notifications": { - "download_complete": "اكتمل التنزيل", - "game_ready_to_install": "{{title}} جاهز للتثبيت", - "repack_list_updated": "تم تحديث قائمة إعادة التعبئة", - "new_update_available": "إصدار {{version}} متاح", - "restart_to_install_update": "أعد تشغيل Hydra لتثبيت التحديث", - "notification_achievement_unlocked_title": "تم فتح الإنجاز لـ {{game}}", - "notification_achievement_unlocked_body": "{{achievement}} وغيرها {{count}} تم فتحها", - "repack_count_zero": "{{count}} تمت إضافة العبوات", - "repack_count_one": "{{count}} تمت إضافة أعد حزم", - "repack_count_two": "{{count}} تمت إضافة العبوات", - "repack_count_few": "{{count}} تمت إضافة العبوات", - "repack_count_many": "{{count}} تمت إضافة العبوات", - "repack_count_other": "{{count}} تمت إضافة العبوات" + "download_complete": "اكْتِمَالُ التَّنْزِيلِ", + "game_ready_to_install": "{{title}} جَاهِزٌ لِلتَّثْبِيتِ", + "repack_list_updated": "تَمَّ تَحْدِيثُ قَائِمَةِ الإِصْدَارَاتِ الْمُعَادَةِ تَغْلِيفُهَا", + "repack_count_one": "{{count}} إِصْدَارٌ مُعَادٌ تَغْلِيفُهُ أُضِيفَ", + "repack_count_other": "{{count}} إِصْدَارَاتٌ مُعَادَةٌ تَغْلِيفُهَا أُضِيفَتْ", + "new_update_available": "الْإِصْدَارُ {{version}} مَتَوَفِّرٌ", + "restart_to_install_update": "أَعِدْ تَشْغِيلَ Hydra لِتَثْبِيتِ التَّحْدِيثِ", + "notification_achievement_unlocked_title": "تَمَّ فَتْحُ إِنْجَازٍ لِـ {{game}}", + "notification_achievement_unlocked_body": "{{achievement}} وَ{{count}} أُخْرَى تَمَّ فَتْحُهَا" }, "system_tray": { - "open": "افتح Hydra", - "quit": "خروج" + "open": "فَتْحُ Hydra", + "quit": "الْخُرُوجُ" }, "game_card": { - "no_downloads": "لا توجد تنزيلات متاحة" + "no_downloads": "لَا تَوْجَدُ تَنْزِيلَاتٌ مَتَوَفِّرَةٌ" }, "binary_not_found_modal": { - "title": "البرامج غير مثبتة", - "description": "لم يتم العثور على الملفات التنفيذية الخاصة بـ Wine أو Lutris على نظامك", - "instructions": "تحقق من الطريقة الصحيحة لتثبيت أي منها على توزيعة Linux لديك حتى تعمل اللعبة بشكل طبيعي" + "title": "الْبَرَامِجُ غَيْرُ مُثَبَّتَةٍ", + "description": "لَمْ يُعْثَرْ عَلَى مَلَفَّاتٍ تَنْفِيذِيَّةٍ لِـ Wine أَوْ Lutris عَلَى نِظَامِكَ", + "instructions": "تَحَقَّقْ مِنَ الطَّرِيقَةِ الصَّحِيحَةِ لِتَثْبِيتِ أَيٍّ مِنْهُمَا عَلَى تَوْزِيعَةِ Linux لَدَيْكَ لِتَعْمَلَ اللُّعْبَةُ بِشَكْلٍ طَبِيعِيٍّ" }, "modal": { - "close": "زر الإغلاق" + "close": "زِرُّ الإِغْلَاقِ" }, "forms": { - "toggle_password_visibility": "تبديل رؤية كلمة المرور" + "toggle_password_visibility": "تَبْدِيلُ رُؤْيَةِ كَلِمَةِ الْمَرُورِ" }, "user_profile": { - "amount_hours": "{{amount}} ساعات", - "amount_minutes": "{{amount}} دقائق", - "last_time_played": "لعبت آخر مرة {{period}}", - "activity": "النشاط الأخير", - "library": "مكتبة", - "total_play_time": "إجمالي وقت اللعب", - "no_recent_activity_title": "هممم... لا شيء هنا", - "no_recent_activity_description": "لم تلعب أي مباراة مؤخرًا. ", - "display_name": "اسم العرض", - "saving": "توفير", - "save": "يحفظ", - "edit_profile": "تحرير الملف الشخصي", - "saved_successfully": "تم الحفظ بنجاح", - "try_again": "من فضلك، حاول مرة أخرى", - "sign_out_modal_title": "هل أنت متأكد؟", - "cancel": "إلغاء", - "successfully_signed_out": "تم تسجيل الخروج بنجاح", - "sign_out": "تسجيل الخروج", - "playing_for": "اللعب من أجل {{amount}}", - "sign_out_modal_text": "مكتبتك مرتبطة بحسابك الحالي. ", - "add_friends": "أضف أصدقاء", - "add": "يضيف", - "friend_code": "رمز الصديق", - "see_profile": "انظر الملف الشخصي", - "sending": "إرسال", - "friend_request_sent": "تم إرسال طلب الصداقة", - "friends": "أصدقاء", - "friends_list": "قائمة الأصدقاء", - "user_not_found": "لم يتم العثور على المستخدم", - "block_user": "حظر المستخدم", - "add_friend": "إضافة صديق", - "request_sent": "تم إرسال الطلب", - "request_received": "تم استلام الطلب", - "accept_request": "قبول الطلب", - "ignore_request": "تجاهل الطلب", - "cancel_request": "إلغاء الطلب", - "undo_friendship": "التراجع عن الصداقة", - "request_accepted": "تم قبول الطلب", - "user_blocked_successfully": "تم حظر المستخدم بنجاح", - "user_block_modal_text": "هذا سوف يمنع {{displayName}}", - "blocked_users": "المستخدمين المحظورين", - "unblock": "إلغاء الحظر", - "no_friends_added": "ليس لديك أي أصدقاء مضافين", - "pending": "قيد الانتظار", - "no_pending_invites": "ليس لديك أي دعوات معلقة", - "no_blocked_users": "ليس لديك أي مستخدمين محظورين", - "friend_code_copied": "تم نسخ رمز الصديق", - "undo_friendship_modal_text": "سيؤدي هذا إلى التراجع عن صداقتك معه {{displayName}}", - "privacy_hint": "لضبط من يمكنه رؤية هذا، انتقل إلى <0>إعدادات", - "locked_profile": "هذا الملف الشخصي خاص", - "image_process_failure": "فشل أثناء معالجة الصورة", - "required_field": "هذه الخانة مطلوبه", - "displayname_min_length": "يجب أن يتكون اسم العرض من 3 أحرف على الأقل", - "displayname_max_length": "يجب ألا يزيد طول اسم العرض عن 50 حرفًا", - "report_profile": "الإبلاغ عن هذا الملف الشخصي", - "report_reason": "لماذا تقوم بالإبلاغ عن هذا الملف الشخصي؟", - "report_description": "معلومات إضافية", - "report_description_placeholder": "معلومات إضافية", - "report": "تقرير", - "report_reason_hate": "خطاب الكراهية", - "report_reason_sexual_content": "المحتوى الجنسي", - "report_reason_violence": "عنف", - "report_reason_spam": "رسائل إلكترونية مزعجة", - "profile_reported": "تم الإبلاغ عن الملف الشخصي", - "your_friend_code": "رمز صديقك:", - "upload_banner": "تحميل لافتة", - "uploading_banner": "جارٍ تحميل البانر…", - "background_image_updated": "تم تحديث صورة الخلفية", - "report_reason_zero": "آخر", - "report_reason_one": "لماذا تقوم بالإبلاغ عن هذا الملف الشخصي؟", - "report_reason_two": "آخر", - "report_reason_few": "آخر", - "report_reason_many": "آخر", - "report_reason_other": "آخر" + "amount_hours": "{{amount}} سَاعَاتٌ", + "amount_minutes": "{{amount}} دَقَائِقُ", + "last_time_played": "آخِرُ مَرَّةٍ لُعِبَتْ {{period}}", + "activity": "النَّشَاطُ الْأَخِيرُ", + "library": "الْمَكْتَبَةُ", + "total_play_time": "إِجْمَالِيُّ وَقْتِ اللَّعِبِ", + "no_recent_activity_title": "هَمَمْ... لَا شَيْءَ هُنَا", + "no_recent_activity_description": "لَمْ تَلْعَبْ أَيَّ أَلْعَابٍ مُؤَخَّرًا. حَانَ الْوَقْتُ لِتَغْيِيرِ ذَلِكَ!", + "display_name": "اسْمُ الْعَرْضِ", + "saving": "جَارٍ الْحِفْظُ", + "save": "حِفْظٌ", + "edit_profile": "تَحْرِيرُ الْمَلَفِّ الشَّخْصِيِّ", + "saved_successfully": "تَمَّ الْحِفْظُ بِنَجَاحٍ", + "try_again": "الرَّجَاءُ الْمُحَاوَلَةُ مَرَّةً أُخْرَى", + "sign_out_modal_title": "هَلْ أَنْتَ مُتَأَكِّدٌ؟", + "cancel": "إِلْغَاءٌ", + "successfully_signed_out": "تَمَّ تَسْجِيلُ الْخُرُوجِ بِنَجَاحٍ", + "sign_out": "تَسْجِيلُ الْخُرُوجِ", + "playing_for": "جَارِي اللَّعِبُ لِمُدَّةِ {{amount}}", + "sign_out_modal_text": "مَكْتَبَتُكَ مُرْتَبِطَةٌ بِحِسَابِكَ الْحَالِيِّ. عِنْدَ تَسْجِيلِ الْخُرُوجِ، لَنْ تَكُونَ مَكْتَبَتُكَ مَرْئِيَّةً بَعْدَ الْآنِ، وَلَنْ يَتِمَّ حِفْظُ أَيِّ تَقَدُّمٍ. هَلْ تُرِيدُ الْمُتَابَعَةَ مَعَ تَسْجِيلِ الْخُرُوجِ؟", + "add_friends": "إِضَافَةُ الْأَصْدِقَاءِ", + "add": "إِضَافَةٌ", + "friend_code": "رَمْزُ الصَّدِيقِ", + "see_profile": "رُؤْيَةُ الْمَلَفِّ الشَّخْصِيِّ", + "sending": "جَارٍ الْإِرْسَالُ", + "friend_request_sent": "تَمَّ إِرْسَالُ طَلَبِ الصَّدَاقَةِ", + "friends": "الْأَصْدِقَاءُ", + "friends_list": "قَائِمَةُ الْأَصْدِقَاءِ", + "user_not_found": "الْمُسْتَخْدِمُ غَيْرُ مَوْجُودٍ", + "block_user": "حَظْرُ الْمُسْتَخْدِمِ", + "add_friend": "إِضَافَةُ صَدِيقٍ", + "request_sent": "تَمَّ إِرْسَالُ الطَّلَبِ", + "request_received": "تَمَّ اسْتِقْبَالُ الطَّلَبِ", + "accept_request": "قَبُولُ الطَّلَبِ", + "ignore_request": "تَجَاهُلُ الطَّلَبِ", + "cancel_request": "إِلْغَاءُ الطَّلَبِ", + "undo_friendship": "إِلْغَاءُ الصَّدَاقَةِ", + "request_accepted": "تَمَّ قَبُولُ الطَّلَبِ", + "user_blocked_successfully": "تَمَّ حَظْرُ الْمُسْتَخْدِمِ بِنَجَاحٍ", + "user_block_modal_text": "سَيُؤَدِّي هَذَا إِلَى حَظْرِ {{displayName}}", + "blocked_users": "الْمُسْتَخْدِمُونَ الْمَحْظُورُونَ", + "unblock": "إِزَالَةُ الْحَظْرِ", + "no_friends_added": "لَيْسَ لَدَيْكَ أَصْدِقَاءٌ مُضَافُونَ", + "pending": "قَيْدُ الْانْتِظَارِ", + "no_pending_invites": "لَيْسَ لَدَيْكَ دَعَوَاتٌ قَيْدُ الْانْتِظَارِ", + "no_blocked_users": "لَيْسَ لَدَيْكَ مُسْتَخْدِمُونَ مَحْظُورُونَ", + "friend_code_copied": "تَمَّ نَسْخُ رَمْزِ الصَّدِيقِ", + "undo_friendship_modal_text": "سَيُؤَدِّي هَذَا إِلَى إِلْغَاءِ صَدَاقَتِكَ مَعَ {{displayName}}", + "privacy_hint": "لِتَعْدِيلِ مَنْ يُمْكِنُهُ رُؤْيَةُ هَذَا، اذْهَبْ إِلَى <0>الإعْدَادَاتِ", + "locked_profile": "هَذَا الْمَلَفُّ الشَّخْصِيُّ خَاصٌّ", + "image_process_failure": "فَشَلَ أَثْنَاءَ مُعَالَجَةِ الصُّورَةِ", + "required_field": "هَذَا الْحَقْلُ مَطْلُوبٌ", + "displayname_min_length": "يَجِبُ أَنْ يَكُونَ اسْمُ الْعَرْضِ عَلَى الْأَقَلِّ 3 أَحْرُفٍ", + "displayname_max_length": "يَجِبُ أَنْ يَكُونَ اسْمُ الْعَرْضِ عَلَى الْأَكْثَرِ 50 حَرْفًا", + "report_profile": "تَقْرِيرٌ عَنْ هَذَا الْمَلَفِّ الشَّخْصِيِّ", + "report_reason": "لِمَاذَا تُقَدِّمُ تَقْرِيرًا عَنْ هَذَا الْمَلَفِّ الشَّخْصِيِّ؟", + "report_description": "مَعْلُومَاتٌ إِضَافِيَّةٌ", + "report_description_placeholder": "مَعْلُومَاتٌ إِضَافِيَّةٌ", + "report": "تَقْرِيرٌ", + "report_reason_hate": "خِطَابُ الْكُرْهِ", + "report_reason_sexual_content": "مُحْتَوًى جِنْسِيٌّ", + "report_reason_violence": "عُنْفٌ", + "report_reason_spam": "رَاسِلَةٌ عَشْوَائِيَّةٌ", + "report_reason_other": "آخَرُ", + "profile_reported": "تَمَّ تَقْرِيرُ الْمَلَفِّ الشَّخْصِيِّ", + "your_friend_code": "رَمْزُ صَدِيقِكَ:", + "upload_banner": "رَفْعُ لَافِتَةٍ", + "uploading_banner": "جَارٍ رَفْعُ اللَّافِتَةِ...", + "background_image_updated": "تَمَّ تَحْدِيثُ صُورَةِ الْخَلْفِيَّةِ", + "stats": "الإحْصَائِيَّاتُ", + "achievements": "الإِنْجَازَاتُ", + "games": "الْأَلْعَابُ", + "top_percentile": "الْأَفْضَلُ {{percentile}}%", + "ranking_updated_weekly": "التَّرْتِيبُ يُحَدَّثُ أُسْبُوعِيًّا", + "playing": "جَارِي اللَّعِبُ {{game}}", + "achievements_unlocked": "الإِنْجَازَاتُ الْمَفْتُوحَةُ", + "earned_points": "النَّقَاطُ الْمَكْسُوبَةُ", + "show_achievements_on_profile": "عَرْضُ إِنْجَازَاتِكَ عَلَى مَلَفِّكَ الشَّخْصِيِّ", + "show_points_on_profile": "عَرْضُ النَّقَاطِ الْمَكْسُوبَةِ عَلَى مَلَفِّكَ الشَّخْصِيِّ" }, "achievement": { - "achievement_unlocked": "تم فتح الإنجاز", - "user_achievements": "{{displayName}}إنجازات", - "your_achievements": "إنجازاتك", - "unlocked_at": "مقفلة في: {{date}}", - "subscription_needed": "مطلوب اشتراك Hydra Cloud لرؤية هذا المحتوى", - "new_achievements_unlocked": "مفتوح {{achievementCount}} انجازات جديدة من {{gameCount}} ألعاب", - "achievement_progress": "{{unlockedCount}}/{{totalCount}} الإنجازات", - "achievements_unlocked_for_game": "مفتوح {{achievementCount}} انجازات جديدة ل {{gameTitle}}" + "achievement_unlocked": "إِنْجَازٌ مَفْتُوحٌ", + "user_achievements": "إِنْجَازَاتُ {{displayName}}", + "your_achievements": "إِنْجَازَاتُكَ", + "unlocked_at": "تَمَّ الْفَتْحُ فِي: {{date}}", + "subscription_needed": "يَحْتَاجُ اشْتِرَاكُ Hydra Cloud لِرُؤْيَةِ هَذَا الْمُحْتَوَى", + "new_achievements_unlocked": "تَمَّ فَتْحُ {{achievementCount}} إِنْجَازَاتٍ جَدِيدَةٍ مِنْ {{gameCount}} أَلْعَابٍ", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} إِنْجَازَاتٍ", + "achievements_unlocked_for_game": "تَمَّ فَتْحُ {{achievementCount}} إِنْجَازَاتٍ جَدِيدَةٍ لِـ {{gameTitle}}", + "hidden_achievement_tooltip": "هَذَا إِنْجَازٌ مَخْفِيٌّ", + "achievement_earn_points": "اكْسِبْ {{points}} نَقَاطًا بِهَذَا الإِنْجَازِ", + "earned_points": "النَّقَاطُ الْمَكْسُوبَةُ:", + "available_points": "النَّقَاطُ الْمُتَوَفِّرَةُ:", + "how_to_earn_achievements_points": "كَيْفَ تَكْسِبُ نَقَاطَ الإِنْجَازَاتِ؟" }, "hydra_cloud": { - "subscription_tour_title": "اشتراك Hydra كلاود", - "subscribe_now": "اشترك الآن", - "cloud_saving": "الحفظ السحابي", - "cloud_achievements": "احفظ إنجازاتك على السحابة", - "animated_profile_picture": "صور شخصية متحركة", - "premium_support": "دعم متميز", - "show_and_compare_achievements": "عرض ومقارنة إنجازاتك مع المستخدمين الآخرين", - "animated_profile_banner": "لافتة الملف الشخصي المتحركة" + "subscription_tour_title": "اشْتِرَاكُ Hydra Cloud", + "subscribe_now": "اشْتَرِكِ الْآنَ", + "cloud_saving": "حِفْظٌ سَحَابِيٌّ", + "cloud_achievements": "حِفْظُ إِنْجَازَاتِكَ فِي السَّحَابَةِ", + "animated_profile_picture": "صُورُ الْمَلَفِّ الشَّخْصِيِّ الْمُتَحَرِّكَةِ", + "premium_support": "الدَّعْمُ الْمُتَقَدِّمُ", + "show_and_compare_achievements": "عَرْضٌ وَمُقَارَنَةُ إِنْجَازَاتِكَ مَعَ مُسْتَخْدِمِينَ آخَرِينَ", + "animated_profile_banner": "لَافِتَةُ الْمَلَفِّ الشَّخْصِيِّ الْمُتَحَرِّكَةِ", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "لَقَدْ اكْتَشَفْتَ مِيزَةً مِنْ Hydra Cloud!", + "learn_more": "تَعَلَّمْ أَكْثَرَ" } } diff --git a/src/locales/bg/translation.json b/src/locales/bg/translation.json index 3112bb08a..b68e60da4 100644 --- a/src/locales/bg/translation.json +++ b/src/locales/bg/translation.json @@ -46,10 +46,20 @@ "checking_files": "Проверка на {{title}} файловете… ({{percentage}} готово)" }, "catalogue": { - "next_page": "Следваща страница", - "previous_page": "Предишна страница" + "search": "Филтър…", + "developers": "Разработчици", + "genres": "Жанрове", + "tags": "Тагове", + "publishers": "Издатели", + "download_sources": "Източници за изтегляне", + "result_count": "{{resultCount}} резултати", + "filter_count": "{{filterCount}} налични", + "clear_filters": "Изчисти {{filterCount}} избрани" }, "game_details": { + "launch_options": "Опции за стартиране", + "launch_options_description": "Напредналите потребители могат да въведат модификации на своите опции за стартиране (экспериментальный)", + "launch_options_placeholder": "Няма зададен параметър", "open_download_options": "Варианти за изтегляне", "download_options_zero": "Няма варианти за изтегляне", "download_options_one": "{{count}} варианти за изтегляне", @@ -177,6 +187,10 @@ "loading": "Зареждане…" }, "downloads": { + "seeding": "Сийдване", + "stop_seeding": "Спри сийдването", + "resume_seeding": "Продължи сийдването", + "options": "Управление", "resume": "Продължи", "pause": "Пауза", "eta": "Conclusion {{eta}}", @@ -202,6 +216,8 @@ "checking_files": "Проверка на файлове…" }, "settings": { + "seed_after_download_complete": "Сийд след завършване на изтеглянето", + "show_hidden_achievement_description": "Показвай описанието на скритите постижения преди отключването им", "downloads_path": "Инсталационен път", "change": "Актуализиране", "notifications": "Известия", @@ -210,7 +226,7 @@ "real_debrid_api_token_label": "Real-Debrid API токен", "quit_app_instead_hiding": "Не скривайте Hydra при затваряне", "launch_with_system": "Стартиране на Hydra при стартиране на системата", - "general": "Общ", + "general": "Общи", "behavior": "Поведение", "download_sources": "Източници за изтегляне", "language": "Език", @@ -288,6 +304,16 @@ "toggle_password_visibility": "Превключване на видимостта на паролата" }, "user_profile": { + "stats": "Статистики", + "achievements": "Постижения", + "games": "Игри", + "top_percentile": "Топ {{percentile}}%", + "ranking_updated_weekly": "Класацията се актуализира седмично", + "playing": "Играе {{game}}", + "achievements_unlocked": "Отключени постижения", + "earned_points": "Спечелени точки", + "show_achievements_on_profile": "Показвай своите постижения в профила", + "show_points_on_profile": "Показвай спечелените точки в профила", "amount_hours": "{{amount}} часове", "amount_minutes": "{{amount}} минути", "last_time_played": "Последно играно {{period}}", @@ -359,6 +385,11 @@ "background_image_updated": "Обновено фоново изображение" }, "achievement": { + "hidden_achievement_tooltip": "Това е скрито постижение", + "achievement_earn_points": "Спечели {{points}} точки с това постижение", + "earned_points": "Спечелени точки:", + "available_points": "Налични точки:", + "how_to_earn_achievements_points": "Как да спечелиш точки за постижения?", "achievement_unlocked": "Постижението е отключено", "user_achievements": "Постиженията на {{displayName}} ", "your_achievements": "Вашите Постижения", @@ -369,6 +400,9 @@ "achievements_unlocked_for_game": "Отключени {{achievementCount}} нови постижения за {{gameTitle}}" }, "hydra_cloud": { + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Открихте функция на Hydra Cloud!", + "learn_more": "Научете повече", "subscription_tour_title": "Hydra Cloud Абонамент", "subscribe_now": "Абонирай се сега", "cloud_saving": "Запазване в облака", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index c93cad1a9..4e3dcb37c 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -168,7 +168,7 @@ "wine_prefix": "Wine Prefix", "wine_prefix_description": "The Wine prefix used to run this game", "launch_options": "Launch Options", - "launch_options_description": "Advanced users may choose to enter modifications to their launch options", + "launch_options_description": "Advanced users may choose to enter modifications to their launch options (experimental feature)", "launch_options_placeholder": "No parameter specified", "no_download_option_info": "No information available", "backup_deletion_failed": "Failed to delete backup", @@ -178,7 +178,13 @@ "select_folder": "Select folder", "backup_from": "Backup from {{date}}", "custom_backup_location_set": "Custom backup location set", - "no_directory_selected": "No directory selected" + "no_directory_selected": "No directory selected", + "no_write_permission": "Cannot download into this directory. Click here to learn more.", + "reset_achievements": "Reset achievements", + "reset_achievements_description": "This will reset all achievements for {{game}}", + "reset_achievements_title": "Are you sure?", + "reset_achievements_success": "Achievements successfully reset", + "reset_achievements_error": "Failed to reset achievements" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 1c8801765..2a80084f3 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -156,7 +156,7 @@ "wine_prefix": "Prefixo Wine", "wine_prefix_description": "O prefixo Wine que foi utilizado para instalar o jogo", "launch_options": "Opções de Inicialização", - "launch_options_description": "Usuários avançados podem adicionar opções de inicialização no jogo", + "launch_options_description": "Usuários avançados podem adicionar opções de inicialização no jogo (experimental)", "launch_options_placeholder": "Nenhum parâmetro informado", "no_download_option_info": "Sem informações disponíveis", "backup_deletion_failed": "Falha ao apagar backup", @@ -167,7 +167,12 @@ "select_folder": "Selecione a pasta", "manage_files_description": "Gerencie quais arquivos serão feitos backup", "clear": "Limpar", - "no_directory_selected": "Nenhum diretório selecionado" + "no_directory_selected": "Nenhum diretório selecionado", + "reset_achievements": "Resetar conquistas", + "reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}", + "reset_achievements_title": "Tem certeza?", + "reset_achievements_success": "Conquistas resetadas com sucesso", + "reset_achievements_error": "Falha ao resetar conquistas" }, "activation": { "title": "Ativação", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 741f5e16f..92008a5e0 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -167,6 +167,9 @@ "loading_save_preview": "Поиск сохранений…", "wine_prefix": "Префикс Wine", "wine_prefix_description": "Префикс Wine, используемый для запуска этой игры", + "launch_options": "Параметры запуска", + "launch_options_description": "Опытные пользователи могут внести изменения в параметры запуска", + "launch_options_placeholder": "Параметр не указан ", "no_download_option_info": "Информация недоступна", "backup_deletion_failed": "Не удалось удалить резервную копию", "max_number_of_artifacts_reached": "Достигнуто максимальное количество резервных копий для этой игры", @@ -175,7 +178,11 @@ "select_folder": "Выбрать папку", "backup_from": "Резервная копия от {{date}}", "custom_backup_location_set": "Установлено настраиваемое местоположение резервной копии", - "no_directory_selected": "Не выбран каталог" + "no_directory_selected": "Не выбран каталог", + "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.", + "reset_achievements_title": "Вы уверены?", + "reset_achievements_success": "Достижения успешно сброшены", + "reset_achievements_error": "Не удалось сбросить достижения" }, "activation": { "title": "Активировать Hydra", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index 6757fdf51..6fa89c033 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -1,131 +1,423 @@ { "language_name": "Türkçe", + "app": { + "successfully_signed_in": "Başarıyla giriş yapıldı" + }, "home": { - "featured": "Öne çıkan", - "surprise_me": "Şaşırt beni", - "no_results": "Sonuç bulunamadı" + "featured": "Öne Çıkanlar", + "surprise_me": "Beni Şaşırt", + "no_results": "Sonuç bulunamadı", + "start_typing": "Aramak için yazmaya başlayın...", + "hot": "Şu anda popüler", + "weekly": "📅 Haftanın en iyi oyunları", + "achievements": "🏆 Tamamlanacak oyunlar" }, "sidebar": { "catalogue": "Katalog", - "downloads": "İndirmeler", + "downloads": "İndirilenler", "settings": "Ayarlar", - "my_library": "Kütüphane", - "downloading_metadata": "{{title}} (Metadata indiriliyor…)", - "paused": "{{title}} (Duraklatıldı)", + "my_library": "Kütüphanem", + "downloading_metadata": "{{title}} (Meta verileri indiriliyor…)", + "paused": "{{title}} (Durduruldu)", "downloading": "{{title}} ({{percentage}} - İndiriliyor…)", "filter": "Kütüphaneyi filtrele", - "home": "Ana menü" + "home": "Ana Sayfa", + "queued": "{{title}} (Sırada)", + "game_has_no_executable": "Oyun için bir çalıştırılabilir dosya seçilmedi", + "sign_in": "Giriş yap", + "friends": "Arkadaşlar", + "need_help": "Yardıma mı ihtiyacınız var?" }, "header": { - "search": "Ara", - "home": "Ana menü", + "search": "Oyunları ara", + "home": "Ana Sayfa", "catalogue": "Katalog", - "downloads": "İndirmeler", + "downloads": "İndirilenler", "search_results": "Arama sonuçları", - "settings": "Ayarlar" + "settings": "Ayarlar", + "version_available_install": "Sürüm {{version}} mevcut. Yüklemek ve yeniden başlatmak için buraya tıklayın.", + "version_available_download": "Sürüm {{version}} mevcut. İndirmek için buraya tıklayın." }, "bottom_panel": { - "no_downloads_in_progress": "İndirilen bir şey yok", - "downloading_metadata": "{{title}} metadatası indiriliyor…", - "downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Bitiş {{eta}} - {{speed}}" + "no_downloads_in_progress": "Devam eden indirme yok", + "downloading_metadata": "{{title}} meta verileri indiriliyor…", + "downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Tamamlama: {{eta}} - Hız: {{speed}}", + "calculating_eta": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Kalan süre hesaplanıyor…", + "checking_files": "{{title}} dosyaları kontrol ediliyor… ({{percentage}} tamamlandı)" }, "catalogue": { - "next_page": "Sonraki sayfa", - "previous_page": "Önceki sayfa" + "search": "Filtrele…", + "developers": "Geliştiriciler", + "genres": "Türler", + "tags": "Etiketler", + "publishers": "Yayıncılar", + "download_sources": "İndirme kaynakları", + "result_count": "{{resultCount}} sonuç", + "filter_count": "{{filterCount}} mevcut", + "clear_filters": "{{filterCount}} seçili filtreyi temizle" }, "game_details": { "open_download_options": "İndirme seçeneklerini aç", "download_options_zero": "İndirme seçeneği yok", "download_options_one": "{{count}} indirme seçeneği", "download_options_other": "{{count}} indirme seçeneği", - "updated_at": "{{updated_at}} güncellendi", - "install": "İndir", + "updated_at": "{{updated_at}} tarihinde güncellendi", + "install": "Yükle", "resume": "Devam et", - "pause": "Duraklat", + "pause": "Durdur", "cancel": "İptal et", - "remove": "Sil", - "space_left_on_disk": "Diskte {{space}} yer kaldı", - "eta": "Bitiş {{eta}}", - "downloading_metadata": "Metadata indiriliyor…", - "filter": "Repackleri filtrele", + "remove": "Kaldır", + "space_left_on_disk": "Diskte {{space}} boş alan kaldı", + "eta": "{{eta}} tahmini bitiş", + "calculating_eta": "Kalan süre hesaplanıyor…", + "downloading_metadata": "Meta veriler indiriliyor…", + "filter": "Paketleri filtrele", "requirements": "Sistem gereksinimleri", "minimum": "Minimum", "recommended": "Önerilen", - "release_date": "{{date}} tarihinde çıktı", - "publisher": "{{publisher}} tarihinde yayınlandı", - "hours": "saatler", - "minutes": "dakikalar", + "paused": "Durduruldu", + "release_date": "{{date}} tarihinde yayımlandı", + "publisher": "{{publisher}} tarafından yayımlandı", + "hours": "saat", + "minutes": "dakika", "amount_hours": "{{amount}} saat", "amount_minutes": "{{amount}} dakika", - "accuracy": "%{{accuracy}} doğruluk", + "accuracy": "{{accuracy}}% doğruluk", "add_to_library": "Kütüphaneye ekle", "remove_from_library": "Kütüphaneden kaldır", - "no_downloads": "İndirme yok", - "play_time": "{{amount}} oynandı", - "last_time_played": "Son oynanan {{period}}", - "not_played_yet": "Bu {{title}} hiç oynanmadı", - "next_suggestion": "Sıradaki öneri", + "no_downloads": "İndirilebilir içerik yok", + "play_time": "{{amount}} süre oynandı", + "last_time_played": "Son oynama {{period}} önce", + "not_played_yet": "{{title}} henüz oynanmadı", + "next_suggestion": "Sonraki öneri", "play": "Oyna", - "deleting": "Installer siliniyor…", + "deleting": "Yükleyici siliniyor…", "close": "Kapat", - "playing_now": "Şimdi oynanıyor", + "playing_now": "Şu anda oynanıyor", "change": "Değiştir", - "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", - "download_now": "Şimdi" + "repacks_modal_description": "İndirmek istediğiniz paketi seçin", + "select_folder_hint": "Varsayılan klasörü değiştirmek için <0>Ayarlar bölümüne gidin", + "download_now": "Şimdi indir", + "no_shop_details": "Mağaza bilgileri alınamadı.", + "download_options": "İndirme seçenekleri", + "download_path": "İndirme yolu", + "previous_screenshot": "Önceki ekran görüntüsü", + "next_screenshot": "Sonraki ekran görüntüsü", + "screenshot": "{{number}} ekran görüntüsü", + "open_screenshot": "{{number}} ekran görüntüsünü aç", + "download_settings": "İndirme ayarları", + "downloader": "İndirici", + "select_executable": "Seç", + "no_executable_selected": "Hiçbir çalıştırılabilir dosya seçilmedi", + "open_folder": "Klasörü aç", + "open_download_location": "İndirilen dosyaları gör", + "create_shortcut": "Masaüstü kısayolu oluştur", + "clear": "Temizle", + "remove_files": "Dosyaları kaldır", + "remove_from_library_title": "Emin misiniz?", + "remove_from_library_description": "Bu işlem {{game}} oyununu kütüphanenizden kaldıracaktır", + "options": "Seçenekler", + "executable_section_title": "Çalıştırılabilir dosya", + "executable_section_description": "\"Oyna\" tıklandığında çalıştırılacak dosyanın yolu", + "downloads_secion_title": "İndirmeler", + "downloads_section_description": "Bu oyun için güncellemeleri veya diğer sürümleri kontrol edin", + "danger_zone_section_title": "Tehlike bölgesi", + "danger_zone_section_description": "Bu oyunu kütüphanenizden veya Hydra tarafından indirilen dosyaları kaldırın", + "download_in_progress": "İndirme devam ediyor", + "download_paused": "İndirme durduruldu", + "last_downloaded_option": "Son indirilen seçenek", + "create_shortcut_success": "Kısayol başarıyla oluşturuldu", + "create_shortcut_error": "Kısayol oluşturulurken hata oluştu", + "nsfw_content_title": "Bu oyun uygunsuz içerik içeriyor", + "nsfw_content_description": "{{title}} her yaş için uygun olmayabilecek içeriklere sahiptir. Devam etmek istediğinizden emin misiniz?", + "allow_nsfw_content": "Devam et", + "refuse_nsfw_content": "Geri dön", + "stats": "İstatistikler", + "download_count": "İndirme sayısı", + "player_count": "Aktif oyuncular", + "download_error": "Bu indirme seçeneği mevcut değil", + "download": "İndir", + "executable_path_in_use": "\"{{game}}\" tarafından kullanılan çalıştırılabilir dosya", + "warning": "Uyarı:", + "hydra_needs_to_remain_open": "Bu indirmenin tamamlanması için Hydra açık kalmalıdır. Eğer Hydra kapanırsa, ilerleme kaydedilmez.", + "achievements": "Başarılar", + "achievements_count": "Başarılar {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "Bulut kaydı", + "cloud_save_description": "İlerlemenizi buluta kaydedin ve herhangi bir cihazda oynamaya devam edin", + "backups": "Yedekler", + "install_backup": "Yükle", + "delete_backup": "Sil", + "create_backup": "Yeni yedek oluştur", + "last_backup_date": "{{date}} tarihindeki son yedek", + "no_backup_preview": "Bu oyun için kayıtlı oyun bulunamadı", + "restoring_backup": "Yedek geri yükleniyor ({{progress}} tamamlandı)…", + "uploading_backup": "Yedek yükleniyor…", + "no_backups": "Bu oyun için henüz bir yedek oluşturmadınız", + "backup_uploaded": "Yedek yüklendi", + "backup_deleted": "Yedek silindi", + "backup_restored": "Yedek geri yüklendi", + "see_all_achievements": "Tüm başarıları gör", + "sign_in_to_see_achievements": "Başarıları görmek için giriş yapın", + "mapping_method_automatic": "Otomatik", + "mapping_method_manual": "Manuel", + "mapping_method_label": "Eşleme yöntemi", + "files_automatically_mapped": "Dosyalar otomatik olarak eşlendi", + "no_backups_created": "Bu oyun için yedek oluşturulmadı", + "manage_files": "Dosyaları yönet", + "loading_save_preview": "Kayıtlı oyunlar aranıyor…", + "wine_prefix": "Wine Prefix", + "wine_prefix_description": "Bu oyunu çalıştırmak için kullanılan Wine Prefix", + "launch_options": "Başlatma Seçenekleri", + "launch_options_description": "İleri düzey kullanıcılar, başlatma seçeneklerine değişiklikler girebilir (deneysel özellik)", + "launch_options_placeholder": "Belirtilen bir parametre yok", + "no_download_option_info": "Bilgi mevcut değil", + "backup_deletion_failed": "Yedek silinemedi", + "max_number_of_artifacts_reached": "Bu oyun için maksimum yedek sayısına ulaşıldı", + "achievements_not_sync": "Başarılarınızı senkronize etmeyi öğrenin", + "manage_files_description": "Hangi dosyaların yedeklenip geri yükleneceğini yönetin", + "select_folder": "Klasör seç", + "backup_from": "{{date}} tarihinden yedek", + "custom_backup_location_set": "Özel yedekleme konumu ayarlandı", + "no_directory_selected": "Bir dizin seçilmedi", + "no_write_permission": "Bu dizine indirme yapılamaz. Daha fazla bilgi için buraya tıklayın.", + "reset_achievements": "Başarıları sıfırla", + "reset_achievements_description": "Bu işlem {{game}} için tüm başarıları sıfırlar", + "reset_achievements_title": "Emin misiniz?", + "reset_achievements_success": "Başarılar başarıyla sıfırlandı", + "reset_achievements_error": "Başarılar sıfırlanamadı" }, "activation": { - "title": "Hydra'yı aktif et", - "installation_id": "Kurulum ID'si:", - "enter_activation_code": "Aktifleştirme kodunuzu girin", - "message": "Bunu nerede soracağınızı bilmiyorsanız, buna sahip olmamanız gerekiyor.", - "activate": "Aktif et", + "title": "Hydra'yı Aktive Et", + "installation_id": "Kurulum Kimliği:", + "enter_activation_code": "Aktivasyon kodunuzu girin", + "message": "Bunu nereden soracağınızı bilmiyorsanız, bu sizin için olmamalı.", + "activate": "Aktive Et", "loading": "Yükleniyor…" }, "downloads": { - "resume": "Devam et", + "resume": "Devam Et", "pause": "Duraklat", - "eta": "Bitiş {{eta}}", + "eta": "Tamamlama {{eta}}", "paused": "Duraklatıldı", "verifying": "Doğrulanıyor…", "completed": "Tamamlandı", - "cancel": "İptal et", - "filter": "Yüklü oyunları filtrele", + "removed": "İndirilmedi", + "cancel": "İptal Et", + "filter": "İndirilen oyunları filtrele", "remove": "Kaldır", "downloading_metadata": "Metadata indiriliyor…", - "deleting": "Installer siliniyor…", - "delete": "Installer'ı sil", + "deleting": "Yükleyici siliniyor…", + "delete": "Yükleyiciyi kaldır", "delete_modal_title": "Emin misiniz?", - "delete_modal_description": "Bu bilgisayarınızdan tüm kurulum dosyalarını silecek", - "install": "Kur" + "delete_modal_description": "Bu işlem, tüm kurulum dosyalarını bilgisayarınızdan kaldıracaktır", + "install": "Kur", + "download_in_progress": "Devam ediyor", + "queued_downloads": "Sıradaki indirmeler", + "downloads_completed": "Tamamlananlar", + "queued": "Sırada", + "no_downloads_title": "Bomboş", + "no_downloads_description": "Henüz Hydra ile hiçbir şey indirmediniz, ancak başlamak için asla geç değil.", + "checking_files": "Dosyalar kontrol ediliyor…", + "seeding": "Paylaşılıyor", + "stop_seeding": "Paylaşımı durdur", + "resume_seeding": "Paylaşımı sürdür", + "options": "Yönet" }, "settings": { "downloads_path": "İndirme yolu", "change": "Güncelle", "notifications": "Bildirimler", - "enable_download_notifications": "Bir indirme bittiğinde", - "enable_repack_list_notifications": "Yeni bir repack eklendiğinde" + "enable_download_notifications": "Bir indirme tamamlandığında", + "enable_repack_list_notifications": "Yeni bir repack eklendiğinde", + "real_debrid_api_token_label": "Real-Debrid API anahtarı", + "quit_app_instead_hiding": "Hydra'yı kapatırken gizlemeyin", + "launch_with_system": "Hydra'yı sistem başlatıldığında çalıştır", + "general": "Genel", + "behavior": "Davranış", + "download_sources": "İndirme kaynakları", + "language": "Dil", + "real_debrid_api_token": "API Anahtarı", + "enable_real_debrid": "Real-Debrid'i Etkinleştir", + "real_debrid_description": "Real-Debrid, yalnızca internet hızınızla sınırlı olarak hızlı dosya indirmenizi sağlayan sınırsız bir indirici.", + "real_debrid_invalid_token": "Geçersiz API anahtarı", + "real_debrid_api_token_hint": "API anahtarınızı <0>buradan alabilirsiniz", + "real_debrid_free_account_error": "\"{{username}}\" hesabı ücretsiz bir hesaptır. Lütfen Real-Debrid abonesi olun", + "real_debrid_linked_message": "\"{{username}}\" hesabı bağlandı", + "save_changes": "Değişiklikleri Kaydet", + "changes_saved": "Değişiklikler başarıyla kaydedildi", + "download_sources_description": "Hydra, indirme bağlantılarını bu kaynaklardan alacak. Kaynak URL, indirme bağlantılarını içeren bir .json dosyasına doğrudan bir bağlantı olmalıdır.", + "validate_download_source": "Doğrula", + "remove_download_source": "Kaldır", + "add_download_source": "Kaynak ekle", + "download_count_zero": "İndirme seçeneği yok", + "download_count_one": "{{countFormatted}} indirme seçeneği", + "download_count_other": "{{countFormatted}} indirme seçeneği", + "download_source_url": "İndirme kaynağı URL'si", + "add_download_source_description": ".json dosyasının URL'sini girin", + "download_source_up_to_date": "Güncel", + "download_source_errored": "Hatalı", + "sync_download_sources": "Kaynakları senkronize et", + "removed_download_source": "İndirme kaynağı kaldırıldı", + "added_download_source": "İndirme kaynağı eklendi", + "download_sources_synced": "Tüm indirme kaynakları senkronize edildi", + "insert_valid_json_url": "Geçerli bir JSON URL'si girin", + "found_download_option_zero": "Hiçbir indirme seçeneği bulunamadı", + "found_download_option_one": "{{countFormatted}} indirme seçeneği bulundu", + "found_download_option_other": "{{countFormatted}} indirme seçeneği bulundu", + "import": "İçe aktar", + "public": "Herkese açık", + "private": "Gizli", + "friends_only": "Sadece arkadaşlar", + "privacy": "Gizlilik", + "profile_visibility": "Profil görünürlüğü", + "profile_visibility_description": "Profilinizi ve kütüphanenizi kimlerin görebileceğini seçin", + "required_field": "Bu alan gereklidir", + "source_already_exists": "Bu kaynak zaten eklenmiş", + "must_be_valid_url": "Kaynak geçerli bir URL olmalıdır", + "blocked_users": "Engellenen kullanıcılar", + "user_unblocked": "Kullanıcının engeli kaldırıldı", + "enable_achievement_notifications": "Bir başarı kilidi açıldığında", + "launch_minimized": "Hydra'yı küçültülmüş başlat", + "disable_nsfw_alert": "NSFW uyarısını devre dışı bırak", + "seed_after_download_complete": "İndirme tamamlandıktan sonra paylaş", + "show_hidden_achievement_description": "Gizli başarı açıklamalarını kilitlenmeden önce göster" }, "notifications": { "download_complete": "İndirme tamamlandı", - "game_ready_to_install": "{{title}} kuruluma hazır", + "game_ready_to_install": "{{title}} kurulmaya hazır", "repack_list_updated": "Repack listesi güncellendi", - "repack_count_one": "{{count}} yeni repack eklendi", - "repack_count_other": "{{count}} yeni repack eklendi" + "repack_count_one": "{{count}} repack eklendi", + "repack_count_other": "{{count}} repack eklendi", + "new_update_available": "Sürüm {{version}} mevcut", + "restart_to_install_update": "Güncellemeyi yüklemek için Hydra'yı yeniden başlatın", + "notification_achievement_unlocked_title": "{{game}} için başarı kilidi açıldı", + "notification_achievement_unlocked_body": "{{achievement}} ve diğer {{count}} başarılar açıldı" }, "system_tray": { - "open": "Hydra'yı aç", + "open": "Hydra'yı Aç", "quit": "Çık" }, "game_card": { - "no_downloads": "İndirme mevcut değil" + "no_downloads": "İndirilebilir içerik bulunmuyor" }, "binary_not_found_modal": { - "title": "Programlar yüklü değil", - "description": "Sisteminizde Wine veya Lutris çalıştırılabiliri bulunamadı", - "instructions": "Oyunları düzgün şekilde çalıştırmak için Linux distronuza bunlardan birini nasıl yükleyebileceğinize bakın" + "title": "Programlar Yüklü Değil", + "description": "Wine veya Lutris çalıştırılabilir dosyaları sisteminizde bulunamadı", + "instructions": "Oyunun normal çalışabilmesi için bunlardan herhangi birini Linux dağıtımınıza uygun şekilde nasıl kuracağınızı kontrol edin" }, "modal": { - "close": "Kapat tuşu" + "close": "Kapat düğmesi" + }, + "forms": { + "toggle_password_visibility": "Şifre görünürlüğünü değiştir" + }, + "user_profile": { + "amount_hours": "{{amount}} saat", + "amount_minutes": "{{amount}} dakika", + "last_time_played": "Son oynanma {{period}}", + "activity": "Son Etkinlik", + "library": "Kütüphane", + "total_play_time": "Toplam oynama süresi", + "no_recent_activity_title": "Hmmm… burada bir şey yok", + "no_recent_activity_description": "Son zamanlarda hiç oyun oynamamışsınız. Bunu değiştirmenin zamanı geldi!", + "display_name": "Görünen isim", + "saving": "Kaydediliyor", + "save": "Kaydet", + "edit_profile": "Profili Düzenle", + "saved_successfully": "Başarıyla kaydedildi", + "try_again": "Lütfen tekrar deneyin", + "sign_out_modal_title": "Emin misiniz?", + "cancel": "İptal", + "successfully_signed_out": "Başarıyla çıkış yapıldı", + "sign_out": "Çıkış yap", + "playing_for": "{{amount}} oynanıyor", + "sign_out_modal_text": "Kütüphaneniz mevcut hesabınıza bağlı. Çıkış yaptığınızda kütüphaneniz görünür olmayacak ve herhangi bir ilerleme kaydedilmeyecek. Çıkışa devam etmek istiyor musunuz?", + "add_friends": "Arkadaş Ekle", + "add": "Ekle", + "friend_code": "Arkadaş kodu", + "see_profile": "Profili gör", + "sending": "Gönderiliyor", + "friend_request_sent": "Arkadaşlık isteği gönderildi", + "friends": "Arkadaşlar", + "friends_list": "Arkadaş listesi", + "user_not_found": "Kullanıcı bulunamadı", + "block_user": "Kullanıcıyı engelle", + "add_friend": "Arkadaş ekle", + "request_sent": "İstek gönderildi", + "request_received": "İstek alındı", + "accept_request": "İsteği kabul et", + "ignore_request": "İsteği yok say", + "cancel_request": "İsteği iptal et", + "undo_friendship": "Arkadaşlığı sonlandır", + "request_accepted": "İstek kabul edildi", + "user_blocked_successfully": "Kullanıcı başarıyla engellendi", + "user_block_modal_text": "Bu işlem {{displayName}} adlı kullanıcıyı engelleyecek", + "blocked_users": "Engellenen kullanıcılar", + "unblock": "Engeli kaldır", + "no_friends_added": "Hiç arkadaş eklemediniz", + "pending": "Bekliyor", + "no_pending_invites": "Bekleyen davetiniz yok", + "no_blocked_users": "Engellenmiş kullanıcı yok", + "friend_code_copied": "Arkadaş kodu kopyalandı", + "undo_friendship_modal_text": "Bu işlem {{displayName}} ile arkadaşlığınızı sonlandıracak", + "privacy_hint": "Bunu kimin görebileceğini ayarlamak için <0>Ayarlar bölümüne gidin", + "locked_profile": "Bu profil gizli", + "image_process_failure": "Görüntü işleme başarısız oldu", + "required_field": "Bu alan gerekli", + "displayname_min_length": "Görünen isim en az 3 karakter uzunluğunda olmalıdır", + "displayname_max_length": "Görünen isim en fazla 50 karakter uzunluğunda olabilir", + "report_profile": "Bu profili bildir", + "report_reason": "Bu profili neden bildiriyorsunuz?", + "report_description": "Ek bilgi", + "report_description_placeholder": "Ek bilgi", + "report": "Bildir", + "report_reason_hate": "Nefret söylemi", + "report_reason_sexual_content": "Cinsel içerik", + "report_reason_violence": "Şiddet", + "report_reason_spam": "Spam", + "report_reason_other": "Diğer", + "profile_reported": "Profil bildirildi", + "your_friend_code": "Arkadaş kodunuz:", + "upload_banner": "Afiş yükle", + "uploading_banner": "Afiş yükleniyor…", + "background_image_updated": "Arka plan görüntüsü güncellendi", + "stats": "İstatistikler", + "achievements": "Başarılar", + "games": "Oyunlar", + "top_percentile": "En üst {{percentile}}%", + "ranking_updated_weekly": "Sıralama haftalık olarak güncellenir", + "playing": "{{game}} oynanıyor", + "achievements_unlocked": "Başarılar açıldı", + "earned_points": "Kazanılan puanlar", + "show_achievements_on_profile": "Başarılarınızı profilinizde gösterin", + "show_points_on_profile": "Kazandığınız puanları profilinizde gösterin" + }, + "achievement": { + "achievement_unlocked": "Başarı açıldı", + "user_achievements": "{{displayName}}'in Başarıları", + "your_achievements": "Başarılarınız", + "unlocked_at": "Açılma zamanı: {{date}}", + "subscription_needed": "Bu içeriği görmek için bir Hydra Cloud aboneliği gereklidir", + "new_achievements_unlocked": "{{gameCount}} oyundan {{achievementCount}} yeni başarı açıldı", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} başarı", + "achievements_unlocked_for_game": "{{gameTitle}} oyunu için {{achievementCount}} yeni başarı açıldı", + "hidden_achievement_tooltip": "Bu gizli bir başarıdır", + "achievement_earn_points": "Bu başarı ile {{points}} puan kazanın", + "earned_points": "Kazanılan puanlar:", + "available_points": "Mevcut puanlar:", + "how_to_earn_achievements_points": "Başarı puanları nasıl kazanılır?" + }, + "hydra_cloud": { + "subscription_tour_title": "Hydra Cloud Aboneliği", + "subscribe_now": "Şimdi abone olun", + "cloud_saving": "Bulut kaydetme", + "cloud_achievements": "Başarılarınızı buluta kaydedin", + "animated_profile_picture": "Animasyonlu profil resimleri", + "premium_support": "Premium Destek", + "show_and_compare_achievements": "Başarılarınızı diğer kullanıcılarla karşılaştırın ve gösterin", + "animated_profile_banner": "Animasyonlu profil afişi", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Bir Hydra Cloud özelliği keşfettiniz!", + "learn_more": "Daha Fazla Bilgi Edinin" } } diff --git a/src/main/events/auth/get-session-hash.ts b/src/main/events/auth/get-session-hash.ts index 2a17bcf11..c9dd39cc0 100644 --- a/src/main/events/auth/get-session-hash.ts +++ b/src/main/events/auth/get-session-hash.ts @@ -9,6 +9,8 @@ const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { if (!auth) return null; const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload; + if (!payload) return null; + return payload.sessionId; }; diff --git a/src/main/events/cloud-save/get-game-artifacts.ts b/src/main/events/cloud-save/get-game-artifacts.ts index dbdcb8536..3fa8552c7 100644 --- a/src/main/events/cloud-save/get-game-artifacts.ts +++ b/src/main/events/cloud-save/get-game-artifacts.ts @@ -1,7 +1,7 @@ import { HydraApi } from "@main/services"; import { registerEvent } from "../register-event"; import type { GameArtifact, GameShop } from "@types"; -import { SubscriptionRequiredError } from "@shared"; +import { SubscriptionRequiredError, UserNotLoggedInError } from "@shared"; const getGameArtifacts = async ( _event: Electron.IpcMainInvokeEvent, @@ -22,6 +22,10 @@ const getGameArtifacts = async ( return []; } + if (err instanceof UserNotLoggedInError) { + return []; + } + throw err; }); }; diff --git a/src/main/events/hardware/check-folder-write-permission.ts b/src/main/events/hardware/check-folder-write-permission.ts new file mode 100644 index 000000000..c74f01e70 --- /dev/null +++ b/src/main/events/hardware/check-folder-write-permission.ts @@ -0,0 +1,15 @@ +import fs from "node:fs"; + +import { registerEvent } from "../register-event"; + +const checkFolderWritePermission = async ( + _event: Electron.IpcMainInvokeEvent, + path: string +) => + new Promise((resolve) => { + fs.access(path, fs.constants.W_OK, (err) => { + resolve(!err); + }); + }); + +registerEvent("checkFolderWritePermission", checkFolderWritePermission); diff --git a/src/main/events/hardware/get-disk-free-space.ts b/src/main/events/hardware/get-disk-free-space.ts index ca5918651..b5ac86e36 100644 --- a/src/main/events/hardware/get-disk-free-space.ts +++ b/src/main/events/hardware/get-disk-free-space.ts @@ -1,10 +1,10 @@ -import checkDiskSpace from "check-disk-space"; +import disk from "diskusage"; import { registerEvent } from "../register-event"; const getDiskFreeSpace = async ( _event: Electron.IpcMainInvokeEvent, path: string -) => checkDiskSpace(path); +) => disk.check(path); registerEvent("getDiskFreeSpace", getDiskFreeSpace); diff --git a/src/main/events/helpers/parse-launch-options.ts b/src/main/events/helpers/parse-launch-options.ts deleted file mode 100644 index e1b562590..000000000 --- a/src/main/events/helpers/parse-launch-options.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const parseLaunchOptions = (params: string | null): string[] => { - if (params == null || params == "") { - return []; - } - - const paramsSplit = params.split(" "); - - return paramsSplit; -}; diff --git a/src/main/events/index.ts b/src/main/events/index.ts index d40539746..25882c3fc 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -11,6 +11,7 @@ import "./catalogue/get-trending-games"; import "./catalogue/get-publishers"; import "./catalogue/get-developers"; import "./hardware/get-disk-free-space"; +import "./hardware/check-folder-write-permission"; import "./library/add-game-to-library"; import "./library/create-game-shortcut"; import "./library/close-game"; @@ -27,9 +28,12 @@ import "./library/verify-executable-path"; import "./library/remove-game"; import "./library/remove-game-from-library"; import "./library/select-game-wine-prefix"; +import "./library/reset-game-achievements"; import "./misc/open-checkout"; import "./misc/open-external"; import "./misc/show-open-dialog"; +import "./misc/get-features"; +import "./misc/show-item-in-folder"; import "./torrenting/cancel-game-download"; import "./torrenting/pause-game-download"; import "./torrenting/resume-game-download"; @@ -71,7 +75,6 @@ import "./cloud-save/delete-game-artifact"; import "./cloud-save/select-game-backup-path"; import "./notifications/publish-new-repacks-notification"; import { isPortableVersion } from "@main/helpers"; -import "./misc/show-item-in-folder"; ipcMain.handle("ping", () => "pong"); ipcMain.handle("getVersion", () => appVersion); diff --git a/src/main/events/library/open-game.ts b/src/main/events/library/open-game.ts index f43dd1a9d..cf73c8109 100644 --- a/src/main/events/library/open-game.ts +++ b/src/main/events/library/open-game.ts @@ -2,9 +2,7 @@ import { gameRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import { shell } from "electron"; -import { spawn } from "child_process"; import { parseExecutablePath } from "../helpers/parse-executable-path"; -import { parseLaunchOptions } from "../helpers/parse-launch-options"; const openGame = async ( _event: Electron.IpcMainInvokeEvent, @@ -12,21 +10,15 @@ const openGame = async ( executablePath: string, launchOptions: string | null ) => { + // TODO: revisit this for launchOptions const parsedPath = parseExecutablePath(executablePath); - const parsedParams = parseLaunchOptions(launchOptions); await gameRepository.update( { id: gameId }, { executablePath: parsedPath, launchOptions } ); - if (process.platform === "linux" || process.platform === "darwin") { - shell.openPath(parsedPath); - } - - if (process.platform === "win32") { - spawn(parsedPath, parsedParams, { shell: false, detached: true }); - } + shell.openPath(parsedPath); }; registerEvent("openGame", openGame); diff --git a/src/main/events/library/reset-game-achievements.ts b/src/main/events/library/reset-game-achievements.ts new file mode 100644 index 000000000..8d52a3a69 --- /dev/null +++ b/src/main/events/library/reset-game-achievements.ts @@ -0,0 +1,56 @@ +import { gameAchievementRepository, gameRepository } from "@main/repository"; +import { registerEvent } from "../register-event"; +import { findAchievementFiles } from "@main/services/achievements/find-achivement-files"; +import fs from "fs"; +import { achievementsLogger, HydraApi, WindowManager } from "@main/services"; +import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; + +const resetGameAchievements = async ( + _event: Electron.IpcMainInvokeEvent, + gameId: number +) => { + try { + const game = await gameRepository.findOne({ where: { id: gameId } }); + + if (!game) return; + + const achievementFiles = findAchievementFiles(game); + + if (achievementFiles.length) { + for (const achievementFile of achievementFiles) { + achievementsLogger.log(`deleting ${achievementFile.filePath}`); + await fs.promises.rm(achievementFile.filePath); + } + } + + await gameAchievementRepository.update( + { objectId: game.objectID }, + { + unlockedAchievements: null, + } + ); + + await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then( + () => + achievementsLogger.log( + `Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}` + ) + ); + + const gameAchievements = await getUnlockedAchievements( + game.objectID, + game.shop, + true + ); + + WindowManager.mainWindow?.webContents.send( + `on-update-achievements-${game.objectID}-${game.shop}`, + gameAchievements + ); + } catch (error) { + achievementsLogger.error(error); + throw error; + } +}; + +registerEvent("resetGameAchievements", resetGameAchievements); diff --git a/src/main/events/misc/get-features.ts b/src/main/events/misc/get-features.ts new file mode 100644 index 000000000..766c84aa2 --- /dev/null +++ b/src/main/events/misc/get-features.ts @@ -0,0 +1,8 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; + +const getFeatures = async (_event: Electron.IpcMainInvokeEvent) => { + return HydraApi.get("/features", null, { needsAuth: false }); +}; + +registerEvent("getFeatures", getFeatures); diff --git a/src/main/events/misc/open-checkout.ts b/src/main/events/misc/open-checkout.ts index 5d087be00..ba48f03b8 100644 --- a/src/main/events/misc/open-checkout.ts +++ b/src/main/events/misc/open-checkout.ts @@ -1,16 +1,10 @@ import { shell } from "electron"; import { registerEvent } from "../register-event"; -import { - userAuthRepository, - userPreferencesRepository, -} from "@main/repository"; +import { userAuthRepository } from "@main/repository"; import { HydraApi } from "@main/services"; const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { - const [userAuth, userPreferences] = await Promise.all([ - userAuthRepository.findOne({ where: { id: 1 } }), - userPreferencesRepository.findOne({ where: { id: 1 } }), - ]); + const userAuth = await userAuthRepository.findOne({ where: { id: 1 } }); if (!userAuth) { return; @@ -22,7 +16,6 @@ const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { const params = new URLSearchParams({ token: paymentToken, - lng: userPreferences?.language || "en", }); shell.openExternal( diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 0d9f5cbb7..134a74e60 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -8,7 +8,7 @@ import { } from "@main/repository"; import { publishDownloadCompleteNotification } from "../notifications"; import type { DownloadProgress } from "@types"; -import { GofileApi, QiwiApi } from "../hosters"; +import { GofileApi, QiwiApi, DatanodesApi } from "../hosters"; import { PythonRPC } from "../python-rpc"; import { LibtorrentPayload, @@ -277,6 +277,16 @@ export class DownloadManager { save_path: game.downloadPath!, }; } + case Downloader.Datanodes: { + const downloadUrl = await DatanodesApi.getDownloadUrl(game.uri!); + + return { + action: "start", + game_id: game.id, + url: downloadUrl, + save_path: game.downloadPath!, + }; + } case Downloader.Torrent: return { action: "start", diff --git a/src/main/services/hosters/datanodes.ts b/src/main/services/hosters/datanodes.ts new file mode 100644 index 000000000..d77e7d514 --- /dev/null +++ b/src/main/services/hosters/datanodes.ts @@ -0,0 +1,46 @@ +import axios, { AxiosResponse } from "axios"; + +export class DatanodesApi { + private static readonly session = axios.create({}); + + public static async getDownloadUrl(downloadUrl: string): Promise { + const parsedUrl = new URL(downloadUrl); + const pathSegments = parsedUrl.pathname.split("/"); + + const fileCode = decodeURIComponent(pathSegments[1]); + const fileName = decodeURIComponent(pathSegments[pathSegments.length - 1]); + + const payload = new URLSearchParams({ + op: "download2", + id: fileCode, + rand: "", + referer: "https://datanodes.to/download", + method_free: "Free Download >>", + method_premium: "", + adblock_detected: "", + }); + + const response: AxiosResponse = await this.session.post( + "https://datanodes.to/download", + payload, + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Cookie: `lang=english; file_name=${fileName}; file_code=${fileCode};`, + Host: "datanodes.to", + Origin: "https://datanodes.to", + Referer: "https://datanodes.to/download", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", + }, + maxRedirects: 0, validateStatus: (status: number) => status === 302 || status < 400, + } + ); + + if (response.status === 302) { + return response.headers["location"]; + } + + return ""; + } +} diff --git a/src/main/services/hosters/index.ts b/src/main/services/hosters/index.ts index 4c5b18035..8cff7bd25 100644 --- a/src/main/services/hosters/index.ts +++ b/src/main/services/hosters/index.ts @@ -1,2 +1,3 @@ export * from "./gofile"; export * from "./qiwi"; +export * from "./datanodes"; diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 426d7afeb..a7cfcee2a 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -64,7 +64,10 @@ export class WindowManager { this.mainWindow.webContents.session.webRequest.onBeforeSendHeaders( (details, callback) => { - if (details.webContentsId !== this.mainWindow?.webContents.id) { + if ( + details.webContentsId !== this.mainWindow?.webContents.id || + details.url.includes("chatwoot") + ) { return callback(details); } @@ -81,15 +84,11 @@ export class WindowManager { this.mainWindow.webContents.session.webRequest.onHeadersReceived( (details, callback) => { - if (details.webContentsId !== this.mainWindow?.webContents.id) { - return callback(details); - } - - if (details.url.includes("featurebase")) { - return callback(details); - } - - if (details.url.includes("chatwoot")) { + if ( + details.webContentsId !== this.mainWindow?.webContents.id || + details.url.includes("featurebase") || + details.url.includes("chatwoot") + ) { return callback(details); } @@ -277,14 +276,9 @@ export class WindowManager { if (process.platform !== "darwin") { await updateSystemTray(); - tray.addListener("click", () => { + tray.addListener("double-click", () => { if (this.mainWindow) { - if ( - WindowManager.mainWindow?.isMinimized() || - !WindowManager.mainWindow?.isVisible() - ) { - WindowManager.mainWindow?.show(); - } + this.mainWindow.show(); } else { this.createMainWindow(); } diff --git a/src/preload/index.ts b/src/preload/index.ts index 2a8ed69e4..316397d22 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -130,6 +130,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("deleteGameFolder", gameId), getGameByObjectId: (objectId: string) => ipcRenderer.invoke("getGameByObjectId", objectId), + resetGameAchievements: (gameId: number) => + ipcRenderer.invoke("resetGameAchievements", gameId), onGamesRunning: ( cb: ( gamesRunning: Pick[] @@ -150,6 +152,8 @@ contextBridge.exposeInMainWorld("electron", { /* Hardware */ getDiskFreeSpace: (path: string) => ipcRenderer.invoke("getDiskFreeSpace", path), + checkFolderWritePermission: (path: string) => + ipcRenderer.invoke("checkFolderWritePermission", path), /* Cloud save */ uploadSaveGame: ( @@ -226,6 +230,7 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("showOpenDialog", options), showItemInFolder: (path: string) => ipcRenderer.invoke("showItemInFolder", path), + getFeatures: () => ipcRenderer.invoke("getFeatures"), platform: process.platform, /* Auto update */ diff --git a/src/renderer/src/components/modal/modal.tsx b/src/renderer/src/components/modal/modal.tsx index af15feb52..d8d0554d6 100644 --- a/src/renderer/src/components/modal/modal.tsx +++ b/src/renderer/src/components/modal/modal.tsx @@ -46,6 +46,12 @@ export function Modal({ }, [onClose]); const isTopMostModal = () => { + if ( + document.querySelector( + ".featurebase-widget-overlay.featurebase-display-block" + ) + ) + return false; const openModals = document.querySelectorAll("[role=dialog]"); return ( diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index d4dfa0078..32664e032 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -1,6 +1,5 @@ import React, { useId, useMemo, useState } from "react"; import type { RecipeVariants } from "@vanilla-extract/recipes"; -import type { FieldError, FieldErrorsImpl, Merge } from "react-hook-form"; import { EyeClosedIcon, EyeIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; @@ -23,7 +22,7 @@ export interface TextFieldProps HTMLDivElement >; rightContent?: React.ReactNode | null; - error?: FieldError | Merge> | undefined; + error?: string | React.ReactNode; } export const TextField = React.forwardRef( @@ -55,10 +54,7 @@ export const TextField = React.forwardRef( }, [props.type, isPasswordVisible]); const hintContent = useMemo(() => { - if (error && error.message) - return ( - {error.message as string} - ); + if (error) return {error}; if (hint) return {hint}; return null; diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index 745418379..d0797caf2 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -8,6 +8,7 @@ export const DOWNLOADER_NAME = { [Downloader.Gofile]: "Gofile", [Downloader.PixelDrain]: "PixelDrain", [Downloader.Qiwi]: "Qiwi", + [Downloader.Datanodes]: "Datanodes", }; export const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120; diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 09ac7257d..9242d9a6a 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -117,11 +117,7 @@ export function GameDetailsContextProvider({ abortControllerRef.current = abortController; window.electron - .getGameShopDetails( - objectId!, - shop as GameShop, - getSteamLanguage(i18n.language) - ) + .getGameShopDetails(objectId, shop, getSteamLanguage(i18n.language)) .then((result) => { if (abortController.signal.aborted) return; @@ -140,14 +136,14 @@ export function GameDetailsContextProvider({ setIsLoading(false); }); - window.electron.getGameStats(objectId, shop as GameShop).then((result) => { + window.electron.getGameStats(objectId, shop).then((result) => { if (abortController.signal.aborted) return; setStats(result); }); if (userDetails) { window.electron - .getUnlockedAchievements(objectId, shop as GameShop) + .getUnlockedAchievements(objectId, shop) .then((achievements) => { if (abortController.signal.aborted) return; setAchievements(achievements); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index feec8284e..88f3297f1 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -31,7 +31,7 @@ import type { CatalogueSearchPayload, } from "@types"; import type { AxiosProgressEvent } from "axios"; -import type { DiskSpace } from "check-disk-space"; +import type disk from "diskusage"; declare global { declare module "*.svg" { @@ -122,7 +122,7 @@ declare global { ) => void ) => () => Electron.IpcRenderer; onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer; - + resetGameAchievements: (gameId: number) => Promise; /* User preferences */ getUserPreferences: () => Promise; updateUserPreferences: ( @@ -140,7 +140,8 @@ declare global { ) => Promise<{ fingerprint: string }>; /* Hardware */ - getDiskFreeSpace: (path: string) => Promise; + getDiskFreeSpace: (path: string) => Promise; + checkFolderWritePermission: (path: string) => Promise; /* Cloud save */ uploadSaveGame: ( @@ -195,6 +196,7 @@ declare global { options: Electron.OpenDialogOptions ) => Promise; showItemInFolder: (path: string) => Promise; + getFeatures: () => Promise; platform: NodeJS.Platform; /* Auto update */ diff --git a/src/renderer/src/hooks/index.ts b/src/renderer/src/hooks/index.ts index 97f519efd..8140e0cda 100644 --- a/src/renderer/src/hooks/index.ts +++ b/src/renderer/src/hooks/index.ts @@ -6,3 +6,4 @@ export * from "./redux"; export * from "./use-user-details"; export * from "./use-format"; export * from "./use-repacks"; +export * from "./use-feature"; diff --git a/src/renderer/src/hooks/use-feature.ts b/src/renderer/src/hooks/use-feature.ts new file mode 100644 index 000000000..ea682ce41 --- /dev/null +++ b/src/renderer/src/hooks/use-feature.ts @@ -0,0 +1,23 @@ +import { useEffect } from "react"; + +enum Feature { + CheckDownloadWritePermission = "CHECK_DOWNLOAD_WRITE_PERMISSION", +} + +export function useFeature() { + useEffect(() => { + window.electron.getFeatures().then((features) => { + localStorage.setItem("features", JSON.stringify(features || [])); + }); + }, []); + + const isFeatureEnabled = (feature: Feature) => { + const features = JSON.parse(localStorage.getItem("features") || "[]"); + return features.includes(feature); + }; + + return { + isFeatureEnabled, + Feature, + }; +} diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 3328c517b..0679cde84 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -13,6 +13,7 @@ import type { UpdateProfileRequest, UserDetails, } from "@types"; +import * as Sentry from "@sentry/react"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; import { isFuture, isToday } from "date-fns"; @@ -30,6 +31,8 @@ export function useUserDetails() { } = useAppSelector((state) => state.userDetails); const clearUserDetails = useCallback(async () => { + Sentry.setUser(null); + dispatch(setUserDetails(null)); dispatch(setProfileBackground(null)); @@ -44,6 +47,12 @@ export function useUserDetails() { const updateUserDetails = useCallback( async (userDetails: UserDetails) => { + Sentry.setUser({ + id: userDetails.id, + username: userDetails.username, + email: userDetails.email ?? undefined, + }); + dispatch(setUserDetails(userDetails)); window.localStorage.setItem("userDetails", JSON.stringify(userDetails)); }, diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 221c27269..61c561f12 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -34,6 +34,19 @@ const Achievements = React.lazy( () => import("./pages/achievements/achievements") ); +import * as Sentry from "@sentry/react"; + +Sentry.init({ + dsn: import.meta.env.RENDERER_VITE_SENTRY_DSN, + integrations: [ + Sentry.browserTracingIntegration(), + Sentry.replayIntegration(), + ], + tracesSampleRate: 1.0, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, +}); + console.log = logger.log; const isStaging = await window.electron.isStaging(); diff --git a/src/renderer/src/pages/catalogue/game-item.tsx b/src/renderer/src/pages/catalogue/game-item.tsx index 8fb4c1b63..184548706 100644 --- a/src/renderer/src/pages/catalogue/game-item.tsx +++ b/src/renderer/src/pages/catalogue/game-item.tsx @@ -31,11 +31,11 @@ export function GameItem({ game }: GameItemProps) { const genres = useMemo(() => { return game.genres?.map((genre) => { - const index = steamGenres["en"].findIndex( + const index = steamGenres["en"]?.findIndex( (steamGenre) => steamGenre === genre ); - if (steamGenres[language] && steamGenres[language][index]) { + if (index && steamGenres[language] && steamGenres[language][index]) { return steamGenres[language][index]; } diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx index b4665f15c..3e8cc7f1f 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx @@ -88,6 +88,9 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { } }, [getGameBackupPreview, visible]); + const userDetails = useAppSelector((state) => state.userDetails.userDetails); + const backupsPerGameLimit = userDetails?.quirks?.backupsPerGameLimit ?? 0; + const backupStateLabel = useMemo(() => { if (uploadingBackup) { return ( @@ -120,7 +123,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { ); } - if (artifacts.length >= 2) { + if (artifacts.length >= backupsPerGameLimit) { return t("max_number_of_artifacts_reached"); } @@ -140,14 +143,12 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { restoringBackup, loadingPreview, artifacts, + backupsPerGameLimit, t, ]); const disableActions = uploadingBackup || restoringBackup || deletingArtifact; - const userDetails = useAppSelector((state) => state.userDetails.userDetails); - const backupsPerGameLimit = userDetails?.quirks.backupsPerGameLimit ?? 0; - return ( (null); + const [diskFreeSpace, setDiskFreeSpace] = useState(null); const [selectedPath, setSelectedPath] = useState(""); const [downloadStarting, setDownloadStarting] = useState(false); const [selectedDownloader, setSelectedDownloader] = useState(null); + const [hasWritePermission, setHasWritePermission] = useState( + null + ); + + const { isFeatureEnabled, Feature } = useFeature(); const userPreferences = useAppSelector( (state) => state.userPreferences.value ); + const getDiskFreeSpace = (path: string) => { + window.electron.getDiskFreeSpace(path).then((result) => { + setDiskFreeSpace(result.free); + }); + }; + + const checkFolderWritePermission = useCallback( + async (path: string) => { + if (isFeatureEnabled(Feature.CheckDownloadWritePermission)) { + const result = await window.electron.checkFolderWritePermission(path); + setHasWritePermission(result); + } else { + setHasWritePermission(true); + } + }, + [Feature, isFeatureEnabled] + ); + useEffect(() => { if (visible) { getDiskFreeSpace(selectedPath); + checkFolderWritePermission(selectedPath); } - }, [visible, selectedPath]); + }, [visible, checkFolderWritePermission, selectedPath]); const downloaders = useMemo(() => { return getDownloadersForUris(repack?.uris ?? []); @@ -84,12 +107,6 @@ export function DownloadSettingsModal({ userPreferences?.realDebridApiToken, ]); - const getDiskFreeSpace = (path: string) => { - window.electron.getDiskFreeSpace(path).then((result) => { - setDiskFreeSpace(result); - }); - }; - const handleChooseDownloadsPath = async () => { const { filePaths } = await window.electron.showOpenDialog({ defaultPath: selectedPath, @@ -124,7 +141,7 @@ export function DownloadSettingsModal({ visible={visible} title={t("download_settings")} description={t("space_left_on_disk", { - space: formatBytes(diskFreeSpace?.free ?? 0), + space: formatBytes(diskFreeSpace ?? 0), })} onClose={onClose} > @@ -168,23 +185,32 @@ export function DownloadSettingsModal({ gap: `${SPACING_UNIT}px`, }} > -
- - - -
+ + {t("no_write_permission")} + + ) : undefined + } + rightContent={ + + } + />

@@ -195,7 +221,11 @@ export function DownloadSettingsModal({ + + + + + + + + ); +} diff --git a/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx b/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx index cc3ed69fa..b9e1927fd 100644 --- a/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx +++ b/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx @@ -163,7 +163,7 @@ export function EditProfileModal( minLength={3} maxLength={50} containerProps={{ style: { width: "100%" } }} - error={errors.displayName} + error={errors.displayName?.message} /> diff --git a/src/renderer/src/pages/profile/report-profile/report-profile.tsx b/src/renderer/src/pages/profile/report-profile/report-profile.tsx index ff73a695d..f859b7f36 100644 --- a/src/renderer/src/pages/profile/report-profile/report-profile.tsx +++ b/src/renderer/src/pages/profile/report-profile/report-profile.tsx @@ -105,7 +105,7 @@ export function ReportProfile() { {...register("description")} label={t("report_description")} placeholder={t("report_description_placeholder")} - error={errors.description} + error={errors.description?.message} />