Compare commits

..

27 Commits

Author SHA1 Message Date
Nate
ee0e314b29 css fix 2025-01-18 18:45:54 -03:00
Nate
d50bb137e6 fixed everything 2025-01-18 15:17:52 -03:00
Nate
1bbf3b27bf added "as vars;" + vars.$ 2025-01-18 14:13:57 -03:00
Nate
3c9d036efd @import to @use 2025-01-18 13:48:00 -03:00
Nate
e97a6fe51a font syntax fix 2025-01-18 13:14:59 -03:00
Eight
ead094de01 Merge branch 'main' into feature/migration-to-scss 2025-01-18 13:08:30 -03:00
Nate
855a646d23 syntax fix 2025-01-17 22:42:14 -03:00
Nate
8192e5d8f1 full migration to scss 2025-01-17 20:16:57 -03:00
Nate
2bd4b69926 full migration to scss 2025-01-17 20:14:54 -03:00
Nate
62c6071395 full migration to scss 2025-01-17 20:03:20 -03:00
Nate
d1750fff59 full migration to scss 2025-01-17 20:00:00 -03:00
Nate
138244d5aa full migration to scss 2025-01-17 19:58:16 -03:00
Nate
d038398750 full migration to scss 2025-01-17 19:43:41 -03:00
Nate
b0eb7c16cd migration to scss 2025-01-17 19:26:27 -03:00
Nate
691dba26af migration to scss
all tsx files adjusted
added root vars on _variables.scss
2025-01-17 18:52:23 -03:00
Nate
ad330bd7a3 Merge branch 'feature/migration-to-scss' of https://github.com/hydralauncher/hydra into feature/migration-to-scss 2025-01-17 18:51:32 -03:00
Hachi-R
fe8f1b44db lint 2025-01-17 16:34:48 -03:00
Hachi-R
b9c072e7ac feat: integrate monaco editor 2025-01-17 16:34:06 -03:00
Nate
50df38856d migration to scss
no .tsx changes were made, yet
2025-01-17 14:11:35 -03:00
Hachi-R
f5395305eb Merge branch 'feature/migration-to-scss' of https://github.com/hydralauncher/hydra into feature/migration-to-scss 2025-01-17 12:37:33 -03:00
Hachi-R
3f29a78593 fix: show editor devtools in dev 2025-01-17 12:29:03 -03:00
Eight
71f3409275 Merge branch 'main' into feature/migration-to-scss 2025-01-17 12:27:30 -03:00
Hachi-R
c4f5d17b40 refactor: remove redundant condition 2025-01-17 12:25:00 -03:00
Hachi-R
686ec61a99 Merge branch 'main' into feature/migration-to-scss 2025-01-17 12:10:00 -03:00
Hachi-R
fb63ec864c feat: add editor window 2025-01-17 12:00:59 -03:00
Hachi-R
e07297fc53 lint 2025-01-17 10:30:35 -03:00
Hachi-R
c17839ae97 feat: add aparence tab to settings page 2025-01-17 10:27:59 -03:00
359 changed files with 6503 additions and 8903 deletions

View File

@@ -1,9 +1,5 @@
name: Build name: Build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: pull_request on: pull_request
jobs: jobs:

View File

@@ -1,9 +1,5 @@
name: Lint name: Lint
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: pull_request on: pull_request
jobs: jobs:

View File

@@ -1,9 +1,5 @@
name: Release name: Release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: on:
push: push:
branches: main branches: main

2
.gitignore vendored
View File

@@ -14,5 +14,3 @@ aria2/
# Sentry Config File # Sentry Config File
.env.sentry-build-plugin .env.sentry-build-plugin
*storybook.log

View File

@@ -125,10 +125,6 @@ cd hydra
yarn yarn
``` ```
### <a name="install-openssl-11"></a> Instale OpenSSL 1.1
[OpenSSL 1.1](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe) é exigido pelo libtorrent em ambientes Windows.
### <a name="install-python-39"></a> Instale Python 3.9 ### <a name="install-python-39"></a> Instale Python 3.9
Certifique-se de ter o Python 3.9 instalado em sua máquina. Você pode baixá-lo e instalá-lo em [python.org](https://www.python.org/downloads/release/python-3913/). Certifique-se de ter o Python 3.9 instalado em sua máquina. Você pode baixá-lo e instalá-lo em [python.org](https://www.python.org/downloads/release/python-3913/).

View File

@@ -6,6 +6,7 @@ import {
externalizeDepsPlugin, externalizeDepsPlugin,
} from "electron-vite"; } from "electron-vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
import svgr from "vite-plugin-svgr"; import svgr from "vite-plugin-svgr";
import { sentryVitePlugin } from "@sentry/vite-plugin"; import { sentryVitePlugin } from "@sentry/vite-plugin";
@@ -37,13 +38,6 @@ export default defineConfig(({ mode }) => {
build: { build: {
sourcemap: true, sourcemap: true,
}, },
css: {
preprocessorOptions: {
scss: {
api: "modern",
},
},
},
resolve: { resolve: {
alias: { alias: {
"@renderer": resolve("src/renderer/src"), "@renderer": resolve("src/renderer/src"),
@@ -54,6 +48,7 @@ export default defineConfig(({ mode }) => {
plugins: [ plugins: [
svgr(), svgr(),
react(), react(),
vanillaExtractPlugin(),
sentryVitePlugin({ sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN, authToken: process.env.SENTRY_AUTH_TOKEN,
org: "hydra-launcher", org: "hydra-launcher",

View File

@@ -1,6 +1,6 @@
{ {
"name": "hydralauncher", "name": "hydralauncher",
"version": "3.2.2", "version": "3.1.5",
"description": "Hydra", "description": "Hydra",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "Los Broxas", "author": "Los Broxas",
@@ -42,10 +42,12 @@
"@reduxjs/toolkit": "^2.2.3", "@reduxjs/toolkit": "^2.2.3",
"@sentry/react": "^8.47.0", "@sentry/react": "^8.47.0",
"@sentry/vite-plugin": "^2.22.7", "@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", "auto-launch": "^5.0.6",
"axios": "^1.7.9", "axios": "^1.7.9",
"better-sqlite3": "^11.7.0", "better-sqlite3": "^11.7.0",
"classic-level": "^2.0.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"color": "^4.2.3", "color": "^4.2.3",
"color.js": "^1.2.0", "color.js": "^1.2.0",
@@ -60,7 +62,6 @@
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^7.2.1",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"kill-port": "^2.0.1",
"knex": "^3.1.0", "knex": "^3.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"parse-torrent": "^11.0.17", "parse-torrent": "^11.0.17",
@@ -74,6 +75,7 @@
"sound-play": "^1.1.0", "sound-play": "^1.1.0",
"sudo-prompt": "^9.2.1", "sudo-prompt": "^9.2.1",
"tar": "^7.4.3", "tar": "^7.4.3",
"typeorm": "^0.3.20",
"user-agents": "^1.1.387", "user-agents": "^1.1.387",
"yaml": "^2.6.1", "yaml": "^2.6.1",
"yup": "^1.5.0", "yup": "^1.5.0",
@@ -89,8 +91,9 @@
"@swc/core": "^1.4.16", "@swc/core": "^1.4.16",
"@types/auto-launch": "^5.0.5", "@types/auto-launch": "^5.0.5",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/folder-hash": "^4.0.4",
"@types/jsdom": "^21.1.7", "@types/jsdom": "^21.1.7",
"@types/jsonwebtoken": "^9.0.8", "@types/jsonwebtoken": "^9.0.7",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.7", "@types/node": "^20.12.7",
"@types/parse-torrent": "^5.8.7", "@types/parse-torrent": "^5.8.7",
@@ -98,13 +101,14 @@
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.18",
"@types/sound-play": "^1.1.3", "@types/sound-play": "^1.1.3",
"@types/user-agents": "^1.0.4", "@types/user-agents": "^1.0.4",
"@vanilla-extract/vite-plugin": "^4.0.7",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"electron": "^31.7.7", "electron": "^31.7.6",
"electron-builder": "^25.1.8", "electron-builder": "^25.1.8",
"electron-vite": "^2.3.0", "electron-vite": "^2.0.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.4", "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"prettier": "^3.4.2", "prettier": "^3.4.2",

View File

@@ -11,12 +11,11 @@ class HttpDownloader:
) )
) )
def start_download(self, url: str, save_path: str, header: str, out: str = None): def start_download(self, url: str, save_path: str, header: str):
if self.download: if self.download:
self.aria2.resume([self.download]) self.aria2.resume([self.download])
else: else:
downloads = self.aria2.add(url, options={"header": header, "dir": save_path, "out": out}) downloads = self.aria2.add(url, options={"header": header, "dir": save_path})
self.download = downloads[0] self.download = downloads[0]
def pause_download(self): def pause_download(self):

View File

@@ -28,14 +28,14 @@ if start_download_payload:
torrent_downloader = TorrentDownloader(torrent_session) torrent_downloader = TorrentDownloader(torrent_session)
downloads[initial_download['game_id']] = torrent_downloader downloads[initial_download['game_id']] = torrent_downloader
try: try:
torrent_downloader.start_download(initial_download['url'], initial_download['save_path']) torrent_downloader.start_download(initial_download['url'], initial_download['save_path'], "")
except Exception as e: except Exception as e:
print("Error starting torrent download", e) print("Error starting torrent download", e)
else: else:
http_downloader = HttpDownloader() http_downloader = HttpDownloader()
downloads[initial_download['game_id']] = http_downloader downloads[initial_download['game_id']] = http_downloader
try: try:
http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'), initial_download.get("out")) http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'))
except Exception as e: except Exception as e:
print("Error starting http download", e) print("Error starting http download", e)
@@ -45,7 +45,7 @@ if start_seeding_payload:
torrent_downloader = TorrentDownloader(torrent_session, lt.torrent_flags.upload_mode) torrent_downloader = TorrentDownloader(torrent_session, lt.torrent_flags.upload_mode)
downloads[seed['game_id']] = torrent_downloader downloads[seed['game_id']] = torrent_downloader
try: try:
torrent_downloader.start_download(seed['url'], seed['save_path']) torrent_downloader.start_download(seed['url'], seed['save_path'], "")
except Exception as e: except Exception as e:
print("Error starting seeding", e) print("Error starting seeding", e)
@@ -94,7 +94,7 @@ def seed_status():
@app.route("/healthcheck", methods=["GET"]) @app.route("/healthcheck", methods=["GET"])
def healthcheck(): def healthcheck():
return "ok", 200 return "", 200
@app.route("/process-list", methods=["GET"]) @app.route("/process-list", methods=["GET"])
def process_list(): def process_list():
@@ -140,18 +140,18 @@ def action():
if url.startswith('magnet'): if url.startswith('magnet'):
if existing_downloader and isinstance(existing_downloader, TorrentDownloader): if existing_downloader and isinstance(existing_downloader, TorrentDownloader):
existing_downloader.start_download(url, data['save_path']) existing_downloader.start_download(url, data['save_path'], "")
else: else:
torrent_downloader = TorrentDownloader(torrent_session) torrent_downloader = TorrentDownloader(torrent_session)
downloads[game_id] = torrent_downloader downloads[game_id] = torrent_downloader
torrent_downloader.start_download(url, data['save_path']) torrent_downloader.start_download(url, data['save_path'], "")
else: else:
if existing_downloader and isinstance(existing_downloader, HttpDownloader): if existing_downloader and isinstance(existing_downloader, HttpDownloader):
existing_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) existing_downloader.start_download(url, data['save_path'], data.get('header'))
else: else:
http_downloader = HttpDownloader() http_downloader = HttpDownloader()
downloads[game_id] = http_downloader downloads[game_id] = http_downloader
http_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) http_downloader.start_download(url, data['save_path'], data.get('header'))
downloading_game_id = game_id downloading_game_id = game_id
@@ -159,8 +159,6 @@ def action():
downloader = downloads.get(game_id) downloader = downloads.get(game_id)
if downloader: if downloader:
downloader.pause_download() downloader.pause_download()
if downloading_game_id == game_id:
downloading_game_id = -1 downloading_game_id = -1
elif action == 'cancel': elif action == 'cancel':
downloader = downloads.get(game_id) downloader = downloads.get(game_id)
@@ -169,7 +167,7 @@ def action():
elif action == 'resume_seeding': elif action == 'resume_seeding':
torrent_downloader = TorrentDownloader(torrent_session, lt.torrent_flags.upload_mode) torrent_downloader = TorrentDownloader(torrent_session, lt.torrent_flags.upload_mode)
downloads[game_id] = torrent_downloader downloads[game_id] = torrent_downloader
torrent_downloader.start_download(data['url'], data['save_path']) torrent_downloader.start_download(data['url'], data['save_path'], "")
elif action == 'pause_seeding': elif action == 'pause_seeding':
downloader = downloads.get(game_id) downloader = downloads.get(game_id)
if downloader: if downloader:

View File

@@ -102,7 +102,7 @@ class TorrentDownloader:
"http://bvarf.tracker.sh:2086/announce", "http://bvarf.tracker.sh:2086/announce",
] ]
def start_download(self, magnet: str, save_path: str): def start_download(self, magnet: str, save_path: str, header: str):
params = {'url': magnet, 'save_path': save_path, 'trackers': self.trackers, 'flags': self.flags} params = {'url': magnet, 'save_path': save_path, 'trackers': self.trackers, 'flags': self.flags}
self.torrent_handle = self.session.add_torrent(params) self.torrent_handle = self.session.add_torrent(params)
self.torrent_handle.resume() self.torrent_handle.resume()

View File

@@ -107,10 +107,7 @@ const copyAria2Macos = async () => {
}; };
const copyAria2 = () => { const copyAria2 = () => {
const aria2Path = if (fs.existsSync("aria2")) {
process.platform === "win32" ? "aria2/aria2c.exe" : "aria2/aria2c";
if (fs.existsSync(aria2Path)) {
console.log("Aria2 already exists, skipping download..."); console.log("Aria2 already exists, skipping download...");
return; return;
} }

View File

@@ -49,14 +49,14 @@ fs.readdir(dist, async (err, files) => {
}) })
); );
for (const upload of uploads) { if (uploads.length > 0) {
await fetch(process.env.BUILD_WEBHOOK_URL, { await fetch(process.env.BUILD_WEBHOOK_URL, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
upload, uploads,
branchName: process.env.BRANCH_NAME, branchName: process.env.BRANCH_NAME,
version: packageJson.version, version: packageJson.version,
githubActor: process.env.GITHUB_ACTOR, githubActor: process.env.GITHUB_ACTOR,

File diff suppressed because one or more lines are too long

View File

@@ -1,475 +1,417 @@
{ {
"language_name": "العربية", "language_name": "اَلْعَرَبِيَّةُ",
"app": { "app": {
"successfully_signed_in": "تم تسجيل الدخول بنجاح" "successfully_signed_in": "تم تسجيل الدخول بنجاح"
}, },
"home": { "home": {
"featured": ميز", "featured": ُتَمَيِّز",
"surprise_me": "مفاجئني", "surprise_me": "فَاجِئْنِي",
"no_results": م يتم العثور على نتائج", "no_results": َمْ يُعْثَرْ عَلَى نَتائِج",
"start_typing": "ابدأ بالكتابة للبحث...", "start_typing": "اِبْدَأْ بِالْكِتَابَةِ لِلْبَحْثِ...",
"hot": "الأكثر شهرة الآن", "hot": "اَلْأَكْثَرُ شُيُوعًا الْآن",
"weekly": "📅 أفضل ألعاب الأسبوع", "weekly": "📅 أَفْضَلُ أَلْعَابِ الْأُسْبُوعِ",
"achievements": "🏆 ألعاب يجب إكمالها" "achievements": "🏆 أَلْعَابٌ لِلتَّغَلُّبِ عَلَيْهَا"
}, },
"sidebar": { "sidebar": {
"catalogue": "الفهرس", "catalogue": "الْفِهْرِسُ",
"downloads": "التنزيلات", "downloads": "التَّنْزِيلَاتُ",
"settings": "الإعدادات", "settings": "الإعْدَادَاتُ",
"my_library": كتبتي", "my_library": َكْتَبَتِي",
"downloading_metadata": "{{title}} (جاري تنزيل البيانات الوصفية...)", "downloading_metadata": "{{title}} (جَارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ...)",
"paused": "{{title}} (معلق)", "paused": "{{title}} (مُوْقَفٌ)",
"downloading": "{{title}} ({{percentage}} - جاري التنزيل...)", "downloading": "{{title}} ({{percentage}} - جَارٍ التَّنْزِيلُ...)",
"filter": صفية المكتبة", "filter": َصْفِيَةُ الْمَكْتَبَةِ",
"home": "الرئيسية", "home": "الرَّئِيسِيَّةُ",
"queued": "{{title}} (في قائمة الانتظار)", "queued": "{{title}} (فِي الْانْتِظَارِ)",
"game_has_no_executable": "اللعبة لا تحتوي على ملف تشغيل", "game_has_no_executable": "اللُّعْبَةُ لَيْسَ لَدَيْهَا مِلَفٌّ تَنْفِيذِيٌّ مُحَدَّدٌ",
"sign_in": سجيل الدخول", "sign_in": َسْجِيلُ الدُّخُولِ",
"friends": "الأصدقاء", "friends": "الْأَصْدِقَاءُ",
"need_help": "تحتاج مساعدة؟", "need_help": "هَلْ تَحْتَاجُ إِلَى مُسَاعَدَةٍ؟"
"favorites": "المفضلة"
}, },
"header": { "header": {
"search": حث الألعاب", "search": َحْثُ الْأَلْعَابِ",
"home": "الرئيسية", "home": "الرَّئِيسِيَّةُ",
"catalogue": "الفهرس", "catalogue": "الْفِهْرِسُ",
"downloads": "التنزيلات", "downloads": "التَّنْزِيلَاتُ",
"search_results": "نتائج البحث", "search_results": َتائِجُ الْبَحْثِ",
"settings": "الإعدادات", "settings": "الإعْدَادَاتُ",
"version_available_install": "الإصدار {{version}} متوفر. انقر هنا لإعادة التشغيل والتثبيت.", "version_available_install": "الْإِصْدَارُ {{version}} مَتَوَفِّرٌ. انْقُرْ هُنَا لِإِعَادَةِ التَّشْغِيلِ وَالتَّثْبِيتِ.",
"version_available_download": "الإصدار {{version}} متوفر. انقر هنا للتنزيل." "version_available_download": "الْإِصْدَارُ {{version}} مَتَوَفِّرٌ. انْقُرْ هُنَا لِلتَّنْزِيلِ."
}, },
"bottom_panel": { "bottom_panel": {
"no_downloads_in_progress": "لا توجد تنزيلات قيد التقدم", "no_downloads_in_progress": َا تَوْجَدُ تَنْزِيلَاتٌ جَارِيَةٌ",
"downloading_metadata": "جاري تنزيل بيانات {{title}} الوصفية...", "downloading_metadata": َارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ لِـ {{title}}...",
"downloading": "جاري تنزيل {{title}}... ({{percentage}} مكتمل) - الوقت المتبقي {{eta}} - السرعة {{speed}}", "downloading": َارٍ تَنْزِيلُ {{title}}... ({{percentage}} مَكْتُومٌ) - الِاكْتِمَالُ {{eta}} - {{speed}}",
"calculating_eta": "جاري تنزيل {{title}}... ({{percentage}} مكتمل) - جاري حساب الوقت المتبقي...", "calculating_eta": َارٍ تَنْزِيلُ {{title}}... ({{percentage}} مَكْتُومٌ) - جَارٍ حِسَابُ الْوَقْتِ الْمُتَبَقِّي...",
"checking_files": "جاري فحص ملفات {{title}}... ({{percentage}} مكتمل)" "checking_files": َارٍ التَّحَقُّقُ مِنْ مَلَفَّاتِ {{title}}... ({{percentage}} مَكْتُومٌ)"
}, },
"catalogue": { "catalogue": {
"search": صفية...", "search": َصْفِيَةٌ...",
"developers": "المطورون", "developers": "الْمُطَوِّرُونَ",
"genres": "الأنواع", "genres": "الْأَنْوَاعُ",
"tags": "الوسوم", "tags": "الْعَلَامَاتُ",
"publishers": "الناشرون", "publishers": "النَّاشِرُونَ",
"download_sources": صادر التنزيل", "download_sources": َصَادِرُ التَّنْزِيلِ",
"result_count": "{{resultCount}} نتيجة", "result_count": "{{resultCount}} نَتائِجُ",
"filter_count": "{{filterCount}} متاح", "filter_count": "{{filterCount}} مَتَوَفِّرٌ",
"clear_filters": سح {{filterCount}} المحددة" "clear_filters": َسْحُ {{filterCount}} الْمُخْتَارَةِ"
}, },
"game_details": { "game_details": {
"open_download_options": تح خيارات التنزيل", "open_download_options": َتْحُ خِيَارَاتِ التَّنْزِيلِ",
"download_options_zero": "لا توجد خيارات تنزيل", "download_options_zero": َا تَوْجَدُ خِيَارَاتُ تَنْزِيلٍ",
"download_options_one": "خيار تنزيل واحد", "download_options_one": "{{count}} خِيَارُ تَنْزِيلٍ",
"download_options_other": "{{count}} خيارات تنزيل", "download_options_other": "{{count}} خِيَارَاتُ تَنْزِيلٍ",
"updated_at": م التحديث في {{updated_at}}", "updated_at": َمَّ التَّحْدِيثُ فِي {{updated_at}}",
"install": ثبيت", "install": َثْبِيتٌ",
"resume": "استئناف", "resume": "اسْتِئْنَافٌ",
"pause": "إيقاف مؤقت", "pause": ِيقَافٌ",
"cancel": لغاء", "cancel": ِلْغَاءٌ",
"remove": زالة", "remove": ِزَالَةٌ",
"space_left_on_disk": "{{space}} متبقي على القرص", "space_left_on_disk": "{{space}} مُتَبَقٍّ عَلَى الْقُرْصِ",
"eta": "الانتهاء المتوقع {{eta}}", "eta": "الِاكْتِمَالُ {{eta}}",
"calculating_eta": "جاري حساب الوقت المتبقي...", "calculating_eta": َارٍ حِسَابُ الْوَقْتِ الْمُتَبَقِّي...",
"downloading_metadata": "جاري تنزيل البيانات الوصفية...", "downloading_metadata": َارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ...",
"filter": صفية الإصدارات المعادة", "filter": َصْفِيَةُ الْإِصْدَارَاتِ الْمُعَادِ تَغْلِيفُهَا",
"requirements": تطلبات النظام", "requirements": ُتَطَلَّبَاتُ النِّظَامِ",
"minimum": "الحد الأدنى", "minimum": "الْأَدْنَى",
"recommended": "مستحسن", "recommended": "الْمُوَصَّى بِهِ",
"paused": علق", "paused": ُوْقَفٌ",
"release_date": اريخ الإصدار {{date}}", "release_date": َمَّ الْإِصْدَارُ فِي {{date}}",
"publisher": شر بواسطة {{publisher}}", "publisher": ُشِرَ بِوَاسِطَةِ {{publisher}}",
"hours": "ساعات", "hours": َاعَاتٌ",
"minutes": قائق", "minutes": َقَائِقُ",
"amount_hours": "{{amount}} ساعة", "amount_hours": "{{amount}} سَاعَاتٌ",
"amount_minutes": "{{amount}} دقيقة", "amount_minutes": "{{amount}} دَقَائِقُ",
"accuracy": قة {{accuracy}}%", "accuracy": ِقَّةٌ {{accuracy}}%",
"add_to_library": ضافة إلى المكتبة", "add_to_library": ِضَافَةٌ إِلَى الْمَكْتَبَةِ",
"remove_from_library": زالة من المكتبة", "remove_from_library": ِزَالَةٌ مِنَ الْمَكْتَبَةِ",
"no_downloads": "لا توجد تنزيلات متاحة", "no_downloads": َا تَوْجَدُ تَنْزِيلَاتٌ مَتَوَفِّرَةٌ",
"play_time": "وقت اللعب {{amount}}", "play_time": "لُعِبَ لِمُدَّةِ {{amount}}",
"last_time_played": "آخر مرة لعب {{period}}", "last_time_played": "آخِرُ مَرَّةٍ لُعِبَتْ {{period}}",
"not_played_yet": م تلعب {{title}} بعد", "not_played_yet": َمْ تَلْعَبْ {{title}} بَعْدُ",
"next_suggestion": "الاقتراح التالي", "next_suggestion": "الِاقْتِرَاحُ التَّالِي",
"play": "تشغيل", "play": "لَعِبٌ",
"deleting": "جاري حذف المثبت...", "deleting": َارٍ حَذْفُ الْمُثَبِّتِ...",
"close": غلاق", "close": ِغْلَاقٌ",
"playing_now": "جاري التشغيل الآن", "playing_now": َارِي اللَّعِبُ الْآن",
"change": غيير", "change": َغْيِيرٌ",
"repacks_modal_description": "اختر الإصدار المعاد الذي تريد تنزيله", "repacks_modal_description": "اخْتَرِ الْإِصْدَارَ الْمُعَادَ تَغْلِيفُهُ الَّذِي تُرِيدُ تَنْزِيلَهُ",
"select_folder_hint": تغيير المجلد الافتراضي، انتقل إلى <0>الإعدادات</0>", "select_folder_hint": ِتَغْيِيرِ الْمَجَلَّدِ الافْتِرَاضِيِّ، اذْهَبْ إِلَى <0>الإعْدَادَاتِ</0>",
"download_now": نزيل الآن", "download_now": َنْزِيلٌ الْآن",
"no_shop_details": "تعذر الحصول على تفاصيل المتجر.", "no_shop_details": "لَمْ يَتَمَكَّنْ مِنْ اسْتِرْدَادِ تَفَاصِيلِ الْمَتْجَرِ.",
"download_options": يارات التنزيل", "download_options": ِيَارَاتُ التَّنْزِيلِ",
"download_path": سار التنزيل", "download_path": َسَارُ التَّنْزِيلِ",
"previous_screenshot": قطة الشاشة السابقة", "previous_screenshot": َقْطَةُ الشَّاشَةِ السَّابِقَةُ",
"next_screenshot": قطة الشاشة التالية", "next_screenshot": َقْطَةُ الشَّاشَةِ التَّالِيَةُ",
"screenshot": قطة الشاشة {{number}}", "screenshot": َقْطَةُ الشَّاشَةِ {{number}}",
"open_screenshot": تح لقطة الشاشة {{number}}", "open_screenshot": َتْحُ لَقْطَةِ الشَّاشَةِ {{number}}",
"download_settings": "إعدادات التنزيل", "download_settings": "إعْدَادَاتُ التَّنْزِيلِ",
"downloader": "أداة التنزيل", "downloader": "الْمُنَزِّلُ",
"select_executable": حديد", "select_executable": َحْدِيدٌ",
"no_executable_selected": م يتم تحديد ملف تشغيل", "no_executable_selected": َمْ يُحَدَّدْ مِلَفٌّ تَنْفِيذِيٌّ",
"open_folder": تح المجلد", "open_folder": َتْحُ الْمَجَلَّدِ",
"open_download_location": "عرض الملفات المنزلة", "open_download_location": "مُشَاهَدَةُ الْمَلَفَّاتِ الْمُنَزَّلَةِ",
"create_shortcut": نشاء اختصار على سطح المكتب", "create_shortcut": ِنْشَاءُ طَرِيقٍ مُخْتَصَرٍ عَلَى سَطْحِ الْمَكْتَبِ",
"clear": سح", "clear": َسْحٌ",
"remove_files": زالة الملفات", "remove_files": ِزَالَةُ الْمَلَفَّاتِ",
"remove_from_library_title": "هل أنت متأكد؟", "remove_from_library_title": "هَلْ أَنْتَ مُتَأَكِّدٌ؟",
"remove_from_library_description": يتم إزالة {{game}} من مكتبتك", "remove_from_library_description": َيُؤَدِّي هَذَا إِلَى إِزَالَةِ {{game}} مِنْ مَكْتَبَتِكَ",
"options": يارات", "options": ِيَارَاتٌ",
"executable_section_title": "ملف التشغيل", "executable_section_title": "الْمِلَفُّ التَّنْفِيذِيُّ",
"executable_section_description": سار الملف الذي سيتم تشغيله عند النقر على \"تشغيل\"", "executable_section_description": َسَارُ الْمِلَفِّ الَّذِي سَيَتِمُّ تَنْفِيذُهُ عِنْدَ النَّقْرِ عَلَى \"لَعِبٌ\"",
"downloads_secion_title": "التنزيلات", "downloads_secion_title": "التَّنْزِيلَاتُ",
"downloads_section_description": حقق من التحديثات أو الإصدارات الأخرى لهذه اللعبة", "downloads_section_description": َحَقَّقْ مِنَ التَّحْدِيثَاتِ أَوِ الْإِصْدَارَاتِ الْأُخْرَى لِهَذِهِ اللُّعْبَةِ",
"danger_zone_section_title": نطقة الخطر", "danger_zone_section_title": ِنْطَقَةُ الْخَطَرِ",
"danger_zone_section_description": زالة هذه اللعبة من مكتبتك أو الملفات التي تم تنزيلها بواسطة Hydra", "danger_zone_section_description": ِزَالَةُ هَذِهِ اللُّعْبَةِ مِنْ مَكْتَبَتِكَ أَوِ الْمَلَفَّاتِ الْمُنَزَّلَةِ بِوَاسِطَةِ Hydra",
"download_in_progress": "جاري التنزيل", "download_in_progress": َارٍ التَّنْزِيلُ",
"download_paused": "التنزيل معلق", "download_paused": "التَّنْزِيلُ مُوْقَفٌ",
"last_downloaded_option": يار التنزيل الأخير", "last_downloaded_option": ِيَارُ التَّنْزِيلِ الْأَخِيرُ",
"create_shortcut_success": م إنشاء الاختصار بنجاح", "create_shortcut_success": َمَّ إِنْشَاءُ الطَّرِيقِ الْمُخْتَصَرِ بِنَجَاحٍ",
"create_shortcut_error": طأ في إنشاء الاختصار", "create_shortcut_error": َطَأٌ فِي إِنْشَاءِ الطَّرِيقِ الْمُخْتَصَرِ",
"nsfw_content_title": "هذه اللعبة تحتوي على محتوى غير لائق", "nsfw_content_title": "هَذِهِ اللُّعْبَةُ تَحْتَوِي عَلَى مُحْتَوًى غَيْرِ لَائِقٍ",
"nsfw_content_description": "{{title}} يحتوي على محتوى قد لا يكون مناسبًا لجميع الأعمار. هل تريد المتابعة؟", "nsfw_content_description": "{{title}} تَحْتَوِي عَلَى مُحْتَوًى قَدْ لَا يَكُونُ مُنَاسِبًا لِجَمِيعِ الْأَعْمَارِ. هَلْ أَنْتَ مُتَأَكِّدٌ مِنْ أَنَّكَ تُرِيدُ الْمُتَابَعَةَ؟",
"allow_nsfw_content": "متابعة", "allow_nsfw_content": "الْمُتَابَعَةُ",
"refuse_nsfw_content": "رجوع", "refuse_nsfw_content": "الرُّجُوعُ",
"stats": "الإحصائيات", "stats": "الإحْصَائِيَّاتُ",
"download_count": "التنزيلات", "download_count": "التَّنْزِيلَاتُ",
"player_count": "اللاعبون النشطون", "player_count": "اللَّاعِبُونَ النَّشِطُونَ",
"download_error": "خيار التنزيل هذا غير متاح", "download_error": "هَذَا خِيَارُ التَّنْزِيلِ غَيْرُ مَتَوَفِّرٍ",
"download": نزيل", "download": َنْزِيلٌ",
"executable_path_in_use": "مسار التشغيل مستخدم بالفعل بواسطة \"{{game}}\"", "executable_path_in_use": "الْمِلَفُّ التَّنْفِيذِيُّ مُسْتَخْدَمٌ بِوَاسِطَةِ \"{{game}}\"",
"warning": حذير:", "warning": َنْبِيهٌ:",
"hydra_needs_to_remain_open": هذا التنزيل، يجب أن يظل Hydra مفتوحًا حتى اكتماله. إذا تم إغلاق Hydra قبل الاكتمال، ستفقد تقدمك.", "hydra_needs_to_remain_open": ِهَذَا التَّنْزِيلِ، يَجِبُ أَنْ يَبْقَى Hydra مَفْتُوحًا حَتَّى يَتِمَّ الِاكْتِمَالُ. إِذَا أُغْلِقَ Hydra قَبْلَ الِاكْتِمَالِ، سَتَفْقِدُ تَقَدُّمَكَ.",
"achievements": "الإنجازات", "achievements": "الإِنْجَازَاتُ",
"achievements_count": "الإنجازات {{unlockedCount}}/{{achievementsCount}}", "achievements_count": "الإِنْجَازَاتُ {{unlockedCount}}/{{achievementsCount}}",
"cloud_save": فظ سحابي", "cloud_save": ِفْظٌ سَحَابِيٌّ",
"cloud_save_description": "احفظ تقدمك في السحابة واستمر في اللعب من أي جهاز", "cloud_save_description": "احْفَظْ تَقَدُّمَكَ فِي السَّحَابَةِ وَاسْتَمِرَّ فِي اللَّعِبِ عَلَى أَيِّ جِهَازٍ",
"backups": "النسخ الاحتياطية", "backups": "الْنُسَخُ الِاحْتِيَاطِيَّةُ",
"install_backup": ثبيت", "install_backup": َثْبِيتٌ",
"delete_backup": ذف", "delete_backup": َذْفٌ",
"create_backup": سخة احتياطية جديدة", "create_backup": ُسْخَةٌ احْتِيَاطِيَّةٌ جَدِيدَةٌ",
"last_backup_date": "آخر نسخة احتياطية في {{date}}", "last_backup_date": "آخِرُ نُسْخَةٍ احْتِيَاطِيَّةٍ فِي {{date}}",
"no_backup_preview": م يتم العثور على حفظات لهذا العنوان", "no_backup_preview": َمْ يُعْثَرْ عَلَى أَيِّ أَلْعَابٍ مَحْفُوظَةٍ لِهَذَا الْعُنْوَانِ",
"restoring_backup": "جاري استعادة النسخة الاحتياطية ({{progress}} مكتمل)...", "restoring_backup": َارٍ اسْتِعَادَةُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ ({{progress}} مَكْتُومٌ)...",
"uploading_backup": "جاري رفع النسخة الاحتياطية...", "uploading_backup": َارٍ رَفْعُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ...",
"no_backups": م تقم بإنشاء أي نسخ احتياطية لهذه اللعبة بعد", "no_backups": َمْ تَقُمْ بِإِنْشَاءِ أَيِّ نُسَخٍ احْتِيَاطِيَّةٍ لِهَذِهِ اللُّعْبَةِ بَعْدُ",
"backup_uploaded": م رفع النسخة الاحتياطية", "backup_uploaded": َمَّ رَفْعُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ",
"backup_deleted": م حذف النسخة الاحتياطية", "backup_deleted": َمَّ حَذْفُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ",
"backup_restored": م استعادة النسخة الاحتياطية", "backup_restored": َمَّ اسْتِعَادَةُ النُّسْخَةِ الِاحْتِيَاطِيَّةِ",
"see_all_achievements": رض جميع الإنجازات", "see_all_achievements": َرْضُ جَمِيعِ الإِنْجَازَاتِ",
"sign_in_to_see_achievements": جل الدخول لعرض الإنجازات", "sign_in_to_see_achievements": َجِّلِ الدُّخُولَ لِعَرْضِ الإِنْجَازَاتِ",
"mapping_method_automatic": "تلقائي", "mapping_method_automatic": "آلِيٌّ",
"mapping_method_manual": دوي", "mapping_method_manual": َدَوِيٌّ",
"mapping_method_label": ريقة التعيين", "mapping_method_label": َرِيقَةُ التَّحْدِيدِ",
"files_automatically_mapped": م تعيين الملفات تلقائيًا", "files_automatically_mapped": َمَّ تَحْدِيدُ الْمَلَفَّاتِ تِلْقَائِيًّا",
"no_backups_created": م يتم إنشاء نسخ احتياطية لهذه اللعبة", "no_backups_created": َمْ تُنْشَأْ أَيُّ نُسَخٍ احْتِيَاطِيَّةٍ لِهَذِهِ اللُّعْبَةِ",
"manage_files": دارة الملفات", "manage_files": ِدَارَةُ الْمَلَفَّاتِ",
"loading_save_preview": "جاري البحث عن حفظات اللعبة...", "loading_save_preview": َارٍ الْبَحْثُ عَنْ أَلْعَابٍ مَحْفُوظَةٍ...",
"wine_prefix": "بادئة Wine", "wine_prefix": َادِئَةُ Wine",
"wine_prefix_description": "بادئة Wine المستخدمة لتشغيل هذه اللعبة", "wine_prefix_description": َادِئَةُ Wine الْمُسْتَخْدَمَةُ لِتَشْغِيلِ هَذِهِ اللُّعْبَةِ",
"launch_options": يارات التشغيل", "launch_options": ِيَارَاتُ الْإِطْلَاقِ",
"launch_options_description": مكن للمستخدمين المتقدمين إدخال تعديلات على خيارات التشغيل (ميزة تجريبية)", "launch_options_description": ُمْكِنُ لِلْمُسْتَخْدِمِينَ الْمُتَقَدِّمِينَ إِدْخَالُ تَعْدِيلَاتٍ عَلَى خِيَارَاتِ الْإِطْلَاقِ",
"launch_options_placeholder": ا توجد معلمات محددة", "launch_options_placeholder": َمْ يُحَدَّدْ أَيُّ مُعَامِلٍ",
"no_download_option_info": "لا توجد معلومات متاحة", "no_download_option_info": َا تَوْجَدُ مَعْلُومَاتٌ مَتَوَفِّرَةٌ",
"backup_deletion_failed": شل في حذف النسخة الاحتياطية", "backup_deletion_failed": َشَلَ فِي حَذْفِ النُّسْخَةِ الِاحْتِيَاطِيَّةِ",
"max_number_of_artifacts_reached": م الوصول إلى الحد الأقصى من النسخ الاحتياطية لهذه اللعبة", "max_number_of_artifacts_reached": َمَّ بَلُوغُ الْعَدَدِ الْأَقْصَى لِلنُّسَخِ الِاحْتِيَاطِيَّةِ لِهَذِهِ اللُّعْبَةِ",
"achievements_not_sync": "شاهد كيفية مزامنة إنجازاتك", "achievements_not_sync": "تَعَرَّفْ عَلَى كَيْفِيَّةِ مَزْجِ إِنْجَازَاتِكَ",
"manage_files_description": دارة الملفات التي سيتم نسخها احتياطيًا واستعادتها", "manage_files_description": ِدَارَةُ الْمَلَفَّاتِ الَّتِي سَيَتِمُّ نَسْخُهَا احْتِيَاطِيًّا وَاسْتِعَادَتُهَا",
"select_folder": "حدد المجلد", "select_folder": "تَحْدِيدُ الْمَجَلَّدِ",
"backup_from": سخة احتياطية من {{date}}", "backup_from": ُسْخَةٌ احْتِيَاطِيَّةٌ مِنْ {{date}}",
"custom_backup_location_set": م تعيين موقع نسخ احتياطي مخصص", "custom_backup_location_set": َمَّ تَحْدِيدُ مَوْقِعِ النُّسْخَةِ الِاحْتِيَاطِيَّةِ الْمُخَصَّصِ",
"no_directory_selected": م يتم تحديد مجلد", "no_directory_selected": َمْ يُحَدَّدْ أَيُّ دَلِيلٍ"
"no_write_permission": "لا يمكن التنزيل إلى هذا المجلد. انقر هنا للمزيد من المعلومات.",
"reset_achievements": "إعادة تعيين الإنجازات",
"reset_achievements_description": "سيؤدي هذا إلى إعادة تعيين جميع إنجازات {{game}}",
"reset_achievements_title": "هل أنت متأكد؟",
"reset_achievements_success": "تم إعادة تعيين الإنجازات بنجاح",
"reset_achievements_error": "فشل في إعادة تعيين الإنجازات",
"download_error_gofile_quota_exceeded": "لقد تجاوزت الحصة الشهرية لـ Gofile. يرجى الانتظار حتى إعادة تعيين الحصة.",
"download_error_real_debrid_account_not_authorized": "حساب Real-Debrid الخاص بك غير مصرح له بإجراء تنزيلات جديدة. يرجى مراجعة إعدادات الحساب والمحاولة مرة أخرى.",
"download_error_not_cached_in_real_debrid": "هذا التنزيل غير متوفر على Real-Debrid وجلب حالة التنزيل من Real-Debrid غير متاح حاليًا.",
"download_error_not_cached_in_torbox": "هذا التنزيل غير متوفر على Torbox وجلب حالة التنزيل من Torbox غير متاح حاليًا.",
"game_removed_from_favorites": "تمت إزالة اللعبة من المفضلة",
"game_added_to_favorites": "تمت إضافة اللعبة إلى المفضلة"
}, },
"activation": { "activation": {
"title": فعيل Hydra", "title": َفْعِيلُ Hydra",
"installation_id": عرف التثبيت:", "installation_id": ُعَرِّفُ التَّثْبِيتِ:",
"enter_activation_code": دخل رمز التفعيل الخاص بك", "enter_activation_code": َدْخِلْ رَمْزَ التَّفْعِيلِ",
"message": ذا كنت لا تعرف أين تطلب هذا، فأنت لا يجب أن يكون لديك هذا.", "message": ِذَا كُنْتَ لَا تَعْرِفُ أَيْنَ تَطْلُبُ هَذَا، فَلا يَجِبُ أَنْ تَكُونَ لَدَيْكَ.",
"activate": فعيل", "activate": َفْعِيلٌ",
"loading": "جاري التحميل..." "loading": َارٍ التَّحْمِيلُ..."
}, },
"downloads": { "downloads": {
"resume": "استئناف", "resume": "اسْتِئْنَافٌ",
"pause": "إيقاف مؤقت", "pause": ِيقَافٌ",
"eta": "الانتهاء المتوقع {{eta}}", "eta": "الِاكْتِمَالُ {{eta}}",
"paused": علق", "paused": ُوْقَفٌ",
"verifying": "جاري التحقق...", "verifying": َارٍ التَّحَقُّقُ...",
"completed": كتمل", "completed": َكْتُومٌ",
"removed": "غير منزّل", "removed": "لَمْ يُنَزَّلْ",
"cancel": لغاء", "cancel": ِلْغَاءٌ",
"filter": صفية الألعاب المنزلة", "filter": َصْفِيَةُ الْأَلْعَابِ الْمُنَزَّلَةِ",
"remove": زالة", "remove": ِزَالَةٌ",
"downloading_metadata": "جاري تنزيل البيانات الوصفية...", "downloading_metadata": َارٍ تَنْزِيلُ الْبَيَانَاتِ الْوَصْفِيَّةِ...",
"deleting": "جاري حذف المثبت...", "deleting": َارٍ حَذْفُ الْمُثَبِّتِ...",
"delete": ذف المثبت", "delete": َذْفُ الْمُثَبِّتِ",
"delete_modal_title": "هل أنت متأكد؟", "delete_modal_title": "هَلْ أَنْتَ مُتَأَكِّدٌ؟",
"delete_modal_description": يؤدي هذا إلى إزالة جميع ملفات التثبيت من جهازك", "delete_modal_description": َيُؤَدِّي هَذَا إِلَى إِزَالَةِ جَمِيعِ مَلَفَّاتِ التَّثْبِيتِ مِنْ حَاسُوبِكَ",
"install": ثبيت", "install": َثْبِيتٌ",
"download_in_progress": "قيد التقدم", "download_in_progress": "جَارٍ التَّنْفِيذُ",
"queued_downloads": "التنزيلات في قائمة الانتظار", "queued_downloads": "التَّنْزِيلَاتُ فِي الْانْتِظَارِ",
"downloads_completed": كتملة", "downloads_completed": َكْتُومٌ",
"queued": "في قائمة الانتظار", "queued": ِي الْانْتِظَارِ",
"no_downloads_title": "لا شيء هنا", "no_downloads_title": "فَرَاغٌ تَامٌ",
"no_downloads_description": م تقم بتنزيل أي شيء باستخدام Hydra بعد، ولكن لم يفت الأوان للبدء.", "no_downloads_description": َمْ تَقُمْ بِتَنْزِيلِ أَيِّ شَيْءٍ بِاسْتِخْدَامِ Hydra بَعْدُ، لَكِنَّهُ لَيْسَ مُتَأَخِّرًا لِلْبَدْءِ.",
"checking_files": "جاري فحص الملفات...", "checking_files": َارٍ التَّحَقُّقُ مِنَ الْمَلَفَّاتِ...",
"seeding": "جاري التوزيع", "seeding": "الْبَذْرُ",
"stop_seeding": "إيقاف التوزيع", "stop_seeding": ِيقَافُ الْبَذْرِ",
"resume_seeding": "استئناف التوزيع", "resume_seeding": "اسْتِئْنَافُ الْبَذْرِ",
"options": دارة" "options": ِدَارَةٌ"
}, },
"settings": { "settings": {
"downloads_path": سار التنزيلات", "downloads_path": َسَارُ التَّنْزِيلَاتِ",
"change": حديث", "change": َحْدِيثٌ",
"notifications": "الإشعارات", "notifications": "الإِشْعَارَاتُ",
"enable_download_notifications": ند اكتمال التنزيل", "enable_download_notifications": ِنْدَ اكْتِمَالِ التَّنْزِيلِ",
"enable_repack_list_notifications": ند إضافة إصدار معاد جديد", "enable_repack_list_notifications": ِنْدَ إِضَافَةِ إِصْدَارٍ مُعَادٍ تَغْلِيفِهِ جَدِيدٍ",
"real_debrid_api_token_label": مز Real-Debrid API", "real_debrid_api_token_label": َمْزُ واجهة برمجة التطبيقات Real-Debrid",
"quit_app_instead_hiding": "لا تخفي Hydra عند الإغلاق", "quit_app_instead_hiding": "لا تُخْفِ Hydra عِنْدَ الإِغْلَاقِ",
"launch_with_system": شغيل Hydra مع بدء النظام", "launch_with_system": َشْغِيلُ Hydra عِنْدَ بَدْءِ النِّظَامِ",
"general": "عام", "general": َامٌ",
"behavior": "السلوك", "behavior": "سُلُوكٌ",
"download_sources": صادر التنزيل", "download_sources": َصَادِرُ التَّنْزِيلِ",
"language": "اللغة", "language": "اللُّغَةُ",
"api_token": مز API", "real_debrid_api_token": َمْزُ واجهة برمجة التطبيقات",
"enable_real_debrid": فعيل Real-Debrid", "enable_real_debrid": َمْكِينُ Real-Debrid",
"real_debrid_description": "Real-Debrid هو أداة تنزيل غير مقيدة تتيح لك تنزيل الملفات بسرعة، محدودة فقط بسرعة اتصالك بالإنترنت.", "real_debrid_description": "Real-Debrid هُوَ مُنَزِّلٌ غَيْرُ مَقْيُودٍ يَتِيحُ لَكَ تَنْزِيلَ الْمَلَفَّاتِ بِسُرْعَةٍ، مَحْدُودٌ فَقَطْ بِسُرْعَةِ الْإِنْتَرْنِتِ لَدَيْكَ.",
"debrid_invalid_token": مز API غير صالح", "real_debrid_invalid_token": َمْزُ واجهة برمجة التطبيقات غَيْرُ صَالِحٍ",
"debrid_api_token_hint": مكنك الحصول على رمز API الخاص بك <0>هنا</0>", "real_debrid_api_token_hint": ُمْكِنُكَ الْحُصُولُ عَلَى رَمْزِ واجهة برمجة التطبيقات <0>هُنَا</0>",
"real_debrid_free_account_error": "الحساب \"{{username}}\" حساب مجاني. يرجى الاشتراك في Real-Debrid", "real_debrid_free_account_error": "الْحِسَابُ \"{{username}}\" هُوَ حِسَابٌ مَجَّانِيٌّ. يَرْجَى الِاشْتِرَاكُ فِي Real-Debrid",
"debrid_linked_message": م ربط الحساب \"{{username}}\"", "real_debrid_linked_message": َمَّ رَبْطُ الْحِسَابِ \"{{username}}\"",
"save_changes": فظ التغييرات", "save_changes": ِفْظُ التَّغْيِيرَاتِ",
"changes_saved": م حفظ التغييرات بنجاح", "changes_saved": َمَّ حِفْظُ التَّغْيِيرَاتِ بِنَجَاحٍ",
"download_sources_description": يقوم Hydra بجلب روابط التنزيل من هذه المصادر. يجب أن يكون عنوان URL للمصدر رابطًا مباشرًا لملف .json يحتوي على روابط التنزيل.", "download_sources_description": َتَقُومُ Hydra بِجَلْبِ رَوَابِطِ التَّنْزِيلِ مِنْ هَذِهِ الْمَصَادِرِ. يَجِبُ أَنْ يَكُونَ عُنْوَانُ URL لِلْمَصْدَرِ رَابِطًا مُبَاشِرًا إِلَى مِلَفٍّ .json يَحْتَوِي عَلَى رَوَابِطِ التَّنْزِيلِ.",
"validate_download_source": حقق", "validate_download_source": َصْدِيقٌ",
"remove_download_source": زالة", "remove_download_source": ِزَالَةٌ",
"add_download_source": ضافة مصدر", "add_download_source": ِضَافَةُ مَصْدَرٍ",
"download_count_zero": "لا توجد خيارات تنزيل", "download_count_zero": َا تَوْجَدُ خِيَارَاتُ تَنْزِيلٍ",
"download_count_one": "{{countFormatted}} خيار تنزيل", "download_count_one": "{{countFormatted}} خِيَارُ تَنْزِيلٍ",
"download_count_other": "{{countFormatted}} خيارات تنزيل", "download_count_other": "{{countFormatted}} خِيَارَاتُ تَنْزِيلٍ",
"download_source_url": نوان مصدر التنزيل", "download_source_url": ُنْوَانُ مَصْدَرِ التَّنْزِيلِ",
"add_download_source_description": دخل عنوان URL لملف .json", "add_download_source_description": َدْخِلْ عُنْوَانَ URL لِمِلَفٍّ .json",
"download_source_up_to_date": حدث", "download_source_up_to_date": ُحَدَّثٌ",
"download_source_errored": طأ", "download_source_errored": َطَأٌ",
"sync_download_sources": زامنة المصادر", "sync_download_sources": َزْجُ الْمَصَادِرِ",
"removed_download_source": مت إزالة مصدر التنزيل", "removed_download_source": َمَّ إِزَالَةُ مَصْدَرِ التَّنْزِيلِ",
"added_download_source": مت إضافة مصدر التنزيل", "added_download_source": َمَّتْ إِضَافَةُ مَصْدَرِ التَّنْزِيلِ",
"download_sources_synced": مت مزامنة جميع مصادر التنزيل", "download_sources_synced": َمَّ مَزْجُ جَمِيعِ مَصَادِرِ التَّنْزِيلِ",
"insert_valid_json_url": دخل عنوان JSON صالح", "insert_valid_json_url": َدْخِلْ عُنْوَانَ JSON صَالِحًا",
"found_download_option_zero": م يتم العثور على خيارات تنزيل", "found_download_option_zero": َمْ يُعْثَرْ عَلَى خِيَارِ تَنْزِيلٍ",
"found_download_option_one": "تم العثور على {{countFormatted}} خيار تنزيل", "found_download_option_one": "عُثِرَ عَلَى {{countFormatted}} خِيَارِ تَنْزِيلٍ",
"found_download_option_other": "تم العثور على {{countFormatted}} خيارات تنزيل", "found_download_option_other": "عُثِرَ عَلَى {{countFormatted}} خِيَارَاتِ تَنْزِيلٍ",
"import": "استيراد", "import": "اسْتِيرَادٌ",
"public": "عام", "public": َامٌ",
"private": "خاص", "private": َاصٌ",
"friends_only": "الأصدقاء فقط", "friends_only": "الْأَصْدِقَاءُ فَقَطْ",
"privacy": "الخصوصية", "privacy": "الْخُصُوصِيَّةُ",
"profile_visibility": ؤية الملف الشخصي", "profile_visibility": ُؤْيَةُ الْمَلَفِّ الشَّخْصِيِّ",
"profile_visibility_description": "اختر من يمكنه رؤية ملفك الشخصي ومكتبتك", "profile_visibility_description": "اخْتَرْ مَنْ يُمْكِنُهُ رُؤْيَةُ مَلَفِّكَ الشَّخْصِيِّ وَمَكْتَبَتِكَ",
"required_field": "هذا الحقل مطلوب", "required_field": "هَذَا الْحَقْلُ مَطْلُوبٌ",
"source_already_exists": "هذا المصدر مضاف مسبقًا", "source_already_exists": "تَمَّتْ إِضَافَةُ هَذَا الْمَصْدَرِ مِنْ قَبْلُ",
"must_be_valid_url": جب أن يكون المصدر عنوان URL صالح", "must_be_valid_url": َجِبُ أَنْ يَكُونَ الْمَصْدَرُ عُنْوَانَ URL صَالِحًا",
"blocked_users": "المستخدمون المحظورون", "blocked_users": "الْمُسْتَخْدِمُونَ الْمَحْظُورُونَ",
"user_unblocked": م إلغاء حظر المستخدم", "user_unblocked": َمَّ إِزَالَةُ حَظْرِ الْمُسْتَخْدِمِ",
"enable_achievement_notifications": ند فتح إنجاز", "enable_achievement_notifications": ِنْدَ فَتْحِ إِنْجَازٍ",
"launch_minimized": شغيل Hydra مصغرًا", "launch_minimized": َشْغِيلُ Hydra مُصَغَّرًا",
"disable_nsfw_alert": عطيل تنبيهات المحتوى غير اللائق", "disable_nsfw_alert": َعْطِيلُ تَنْبِيهِ الْمُحْتَوَى غَيْرِ اللَّائِقِ",
"seed_after_download_complete": "التوزيع بعد اكتمال التنزيل", "seed_after_download_complete": "الْبَذْرُ بَعْدَ اكْتِمَالِ التَّنْزِيلِ",
"show_hidden_achievement_description": "عرض وصف الإنجازات المخفية قبل فتحها", "show_hidden_achievement_description": "إِظْهَارُ وَصْفِ الإِنْجَازَاتِ الْمَخْفِيَّةِ قَبْلَ فَتْحِهَا"
"account": "الحساب",
"no_users_blocked": "لا يوجد مستخدمون محظورون",
"subscription_active_until": "اشتراك Hydra Cloud نشط حتى {{date}}",
"manage_subscription": "إدارة الاشتراك",
"update_email": "تحديث البريد الإلكتروني",
"update_password": "تحديث كلمة المرور",
"current_email": "البريد الإلكتروني الحالي:",
"no_email_account": "لم تقم بتعيين بريد إلكتروني بعد",
"account_data_updated_successfully": "تم تحديث بيانات الحساب بنجاح",
"renew_subscription": "تجديد اشتراك Hydra Cloud",
"subscription_expired_at": "انتهى اشتراكك في {{date}}",
"no_subscription": "استمتع بـ Hydra بأفضل طريقة ممكنة",
"become_subscriber": "كن مشتركًا في Hydra Cloud",
"subscription_renew_cancelled": "تم تعطيل التجديد التلقائي",
"subscription_renews_on": "سيتم تجديد اشتراكك في {{date}}",
"bill_sent_until": "سيتم إرسال فاتورتك القادمة حتى هذا اليوم",
"no_themes": "يبدو أنه ليس لديك أي سمات بعد، لكن لا تقلق، انقر هنا لإنشاء أول تحفة فنية لك.",
"editor_tab_code": "الكود",
"editor_tab_info": "معلومات",
"editor_tab_save": "حفظ",
"web_store": "المتجر الإلكتروني",
"clear_themes": "مسح",
"create_theme": "إنشاء",
"create_theme_modal_title": "إنشاء سمة مخصصة",
"create_theme_modal_description": "إنشاء سمة جديدة لتخصيص مظهر Hydra",
"theme_name": "الاسم",
"insert_theme_name": "أدخل اسم السمة",
"set_theme": "تعيين السمة",
"unset_theme": "إلغاء تعيين السمة",
"delete_theme": "حذف السمة",
"edit_theme": "تعديل السمة",
"delete_all_themes": "حذف جميع السمات",
"delete_all_themes_description": "سيؤدي هذا إلى حذف جميع السمات المخصصة الخاصة بك",
"delete_theme_description": "سيؤدي هذا إلى حذف السمة {{theme}}",
"cancel": "إلغاء",
"appearance": "المظهر",
"enable_torbox": "تفعيل Torbox",
"torbox_description": "TorBox هي خدمة seedbox متميزة تنافس أفضل الخوادم في السوق.",
"torbox_account_linked": "تم ربط حساب TorBox",
"real_debrid_account_linked": "تم ربط حساب Real-Debrid",
"name_min_length": "يجب أن يكون اسم السمة على الأقل 3 أحرف",
"import_theme": "استيراد سمة",
"import_theme_description": "ستقوم باستيراد {{theme}} من متجر السمات",
"error_importing_theme": "خطأ في استيراد السمة",
"theme_imported": "تم استيراد السمة بنجاح"
}, },
"notifications": { "notifications": {
"download_complete": "اكتمل التنزيل", "download_complete": "اكْتِمَالُ التَّنْزِيلِ",
"game_ready_to_install": "{{title}} جاهز للتثبيت", "game_ready_to_install": "{{title}} جَاهِزٌ لِلتَّثْبِيتِ",
"repack_list_updated": م تحديث قائمة الإصدارات المعادة", "repack_list_updated": َمَّ تَحْدِيثُ قَائِمَةِ الإِصْدَارَاتِ الْمُعَادَةِ تَغْلِيفُهَا",
"repack_count_one": "تمت إضافة {{count}} إصدار معاد", "repack_count_one": "{{count}} إِصْدَارٌ مُعَادٌ تَغْلِيفُهُ أُضِيفَ",
"repack_count_other": "تمت إضافة {{count}} إصدارات معادة", "repack_count_other": "{{count}} إِصْدَارَاتٌ مُعَادَةٌ تَغْلِيفُهَا أُضِيفَتْ",
"new_update_available": "الإصدار {{version}} متوفر", "new_update_available": "الْإِصْدَارُ {{version}} مَتَوَفِّرٌ",
"restart_to_install_update": عد تشغيل Hydra لتثبيت التحديث", "restart_to_install_update": َعِدْ تَشْغِيلَ Hydra لِتَثْبِيتِ التَّحْدِيثِ",
"notification_achievement_unlocked_title": م فتح إنجاز لـ {{game}}", "notification_achievement_unlocked_title": َمَّ فَتْحُ إِنْجَازٍ لِـ {{game}}",
"notification_achievement_unlocked_body": "{{achievement}} و {{count}} أخرى تم فتحها" "notification_achievement_unlocked_body": "{{achievement}} وَ{{count}} أُخْرَى تَمَّ فَتْحُهَا"
}, },
"system_tray": { "system_tray": {
"open": تح Hydra", "open": َتْحُ Hydra",
"quit": "خروج" "quit": "الْخُرُوجُ"
}, },
"game_card": { "game_card": {
"no_downloads": "لا توجد تنزيلات متاحة" "no_downloads": َا تَوْجَدُ تَنْزِيلَاتٌ مَتَوَفِّرَةٌ"
}, },
"binary_not_found_modal": { "binary_not_found_modal": {
"title": "البرامج غير مثبتة", "title": "الْبَرَامِجُ غَيْرُ مُثَبَّتَةٍ",
"description": م يتم العثور على ملفات تشغيل Wine أو Lutris على نظامك", "description": َمْ يُعْثَرْ عَلَى مَلَفَّاتٍ تَنْفِيذِيَّةٍ لِـ Wine أَوْ Lutris عَلَى نِظَامِكَ",
"instructions": حقق من الطريقة الصحيحة لتثبيت أي منها على توزيعة Linux الخاصة بك حتى تعمل اللعبة بشكل طبيعي" "instructions": َحَقَّقْ مِنَ الطَّرِيقَةِ الصَّحِيحَةِ لِتَثْبِيتِ أَيٍّ مِنْهُمَا عَلَى تَوْزِيعَةِ Linux لَدَيْكَ لِتَعْمَلَ اللُّعْبَةُ بِشَكْلٍ طَبِيعِيٍّ"
}, },
"modal": { "modal": {
"close": ر الإغلاق" "close": ِرُّ الإِغْلَاقِ"
}, },
"forms": { "forms": {
"toggle_password_visibility": بديل رؤية كلمة المرور" "toggle_password_visibility": َبْدِيلُ رُؤْيَةِ كَلِمَةِ الْمَرُورِ"
}, },
"user_profile": { "user_profile": {
"amount_hours": "{{amount}} ساعة", "amount_hours": "{{amount}} سَاعَاتٌ",
"amount_minutes": "{{amount}} دقيقة", "amount_minutes": "{{amount}} دَقَائِقُ",
"last_time_played": "آخر مرة لعب {{period}}", "last_time_played": "آخِرُ مَرَّةٍ لُعِبَتْ {{period}}",
"activity": "النشاط الأخير", "activity": "النَّشَاطُ الْأَخِيرُ",
"library": "المكتبة", "library": "الْمَكْتَبَةُ",
"total_play_time": جمالي وقت اللعب", "total_play_time": ِجْمَالِيُّ وَقْتِ اللَّعِبِ",
"no_recent_activity_title": "لا شيء هنا...", "no_recent_activity_title": "هَمَمْ... لَا شَيْءَ هُنَا",
"no_recent_activity_description": م تلعب أي ألعاب مؤخرًا. حان الوقت لتغيير ذلك!", "no_recent_activity_description": َمْ تَلْعَبْ أَيَّ أَلْعَابٍ مُؤَخَّرًا. حَانَ الْوَقْتُ لِتَغْيِيرِ ذَلِكَ!",
"display_name": "اسم العرض", "display_name": "اسْمُ الْعَرْضِ",
"saving": "جاري الحفظ", "saving": َارٍ الْحِفْظُ",
"save": فظ", "save": ِفْظٌ",
"edit_profile": عديل الملف الشخصي", "edit_profile": َحْرِيرُ الْمَلَفِّ الشَّخْصِيِّ",
"saved_successfully": م الحفظ بنجاح", "saved_successfully": َمَّ الْحِفْظُ بِنَجَاحٍ",
"try_again": "يرجى المحاولة مرة أخرى", "try_again": "الرَّجَاءُ الْمُحَاوَلَةُ مَرَّةً أُخْرَى",
"sign_out_modal_title": "هل أنت متأكد؟", "sign_out_modal_title": "هَلْ أَنْتَ مُتَأَكِّدٌ؟",
"cancel": لغاء", "cancel": ِلْغَاءٌ",
"successfully_signed_out": م تسجيل الخروج بنجاح", "successfully_signed_out": َمَّ تَسْجِيلُ الْخُرُوجِ بِنَجَاحٍ",
"sign_out": سجيل الخروج", "sign_out": َسْجِيلُ الْخُرُوجِ",
"playing_for": "جاري اللعب لمدة {{amount}}", "playing_for": َارِي اللَّعِبُ لِمُدَّةِ {{amount}}",
"sign_out_modal_text": كتبتك مرتبطة بحسابك الحالي. عند تسجيل الخروج، لن تكون مكتبتك مرئية، ولن يتم حفظ أي تقدم. هل تتابع تسجيل الخروج؟", "sign_out_modal_text": َكْتَبَتُكَ مُرْتَبِطَةٌ بِحِسَابِكَ الْحَالِيِّ. عِنْدَ تَسْجِيلِ الْخُرُوجِ، لَنْ تَكُونَ مَكْتَبَتُكَ مَرْئِيَّةً بَعْدَ الْآنِ، وَلَنْ يَتِمَّ حِفْظُ أَيِّ تَقَدُّمٍ. هَلْ تُرِيدُ الْمُتَابَعَةَ مَعَ تَسْجِيلِ الْخُرُوجِ؟",
"add_friends": ضافة أصدقاء", "add_friends": ِضَافَةُ الْأَصْدِقَاءِ",
"add": ضافة", "add": ِضَافَةٌ",
"friend_code": مز الصديق", "friend_code": َمْزُ الصَّدِيقِ",
"see_profile": "عرض الملف الشخصي", "see_profile": "رُؤْيَةُ الْمَلَفِّ الشَّخْصِيِّ",
"sending": "جاري الإرسال", "sending": َارٍ الْإِرْسَالُ",
"friend_request_sent": م إرسال طلب الصداقة", "friend_request_sent": َمَّ إِرْسَالُ طَلَبِ الصَّدَاقَةِ",
"friends": "الأصدقاء", "friends": "الْأَصْدِقَاءُ",
"friends_list": "قائمة الأصدقاء", "friends_list": َائِمَةُ الْأَصْدِقَاءِ",
"user_not_found": "المستخدم غير موجود", "user_not_found": "الْمُسْتَخْدِمُ غَيْرُ مَوْجُودٍ",
"block_user": ظر المستخدم", "block_user": َظْرُ الْمُسْتَخْدِمِ",
"add_friend": ضافة صديق", "add_friend": ِضَافَةُ صَدِيقٍ",
"request_sent": م إرسال الطلب", "request_sent": َمَّ إِرْسَالُ الطَّلَبِ",
"request_received": م استلام الطلب", "request_received": َمَّ اسْتِقْبَالُ الطَّلَبِ",
"accept_request": بول الطلب", "accept_request": َبُولُ الطَّلَبِ",
"ignore_request": جاهل الطلب", "ignore_request": َجَاهُلُ الطَّلَبِ",
"cancel_request": لغاء الطلب", "cancel_request": ِلْغَاءُ الطَّلَبِ",
"undo_friendship": لغاء الصداقة", "undo_friendship": ِلْغَاءُ الصَّدَاقَةِ",
"request_accepted": م قبول الطلب", "request_accepted": َمَّ قَبُولُ الطَّلَبِ",
"user_blocked_successfully": م حظر المستخدم بنجاح", "user_blocked_successfully": َمَّ حَظْرُ الْمُسْتَخْدِمِ بِنَجَاحٍ",
"user_block_modal_text": يؤدي هذا إلى حظر {{displayName}}", "user_block_modal_text": َيُؤَدِّي هَذَا إِلَى حَظْرِ {{displayName}}",
"blocked_users": "المستخدمون المحظورون", "blocked_users": "الْمُسْتَخْدِمُونَ الْمَحْظُورُونَ",
"unblock": لغاء الحظر", "unblock": ِزَالَةُ الْحَظْرِ",
"no_friends_added": يس لديك أصدقاء مضافون", "no_friends_added": َيْسَ لَدَيْكَ أَصْدِقَاءٌ مُضَافُونَ",
"pending": "معلق", "pending": َيْدُ الْانْتِظَارِ",
"no_pending_invites": يس لديك دعوات معلقة", "no_pending_invites": َيْسَ لَدَيْكَ دَعَوَاتٌ قَيْدُ الْانْتِظَارِ",
"no_blocked_users": يس لديك مستخدمون محظورون", "no_blocked_users": َيْسَ لَدَيْكَ مُسْتَخْدِمُونَ مَحْظُورُونَ",
"friend_code_copied": م نسخ رمز الصديق", "friend_code_copied": َمَّ نَسْخُ رَمْزِ الصَّدِيقِ",
"undo_friendship_modal_text": يؤدي هذا إلى إلغاء صداقتك مع {{displayName}}", "undo_friendship_modal_text": َيُؤَدِّي هَذَا إِلَى إِلْغَاءِ صَدَاقَتِكَ مَعَ {{displayName}}",
"privacy_hint": ضبط من يمكنه رؤية هذا، انتقل إلى <0>الإعدادات</0>", "privacy_hint": ِتَعْدِيلِ مَنْ يُمْكِنُهُ رُؤْيَةُ هَذَا، اذْهَبْ إِلَى <0>الإعْدَادَاتِ</0>",
"locked_profile": "هذا الملف الشخصي خاص", "locked_profile": "هَذَا الْمَلَفُّ الشَّخْصِيُّ خَاصٌّ",
"image_process_failure": شل في معالجة الصورة", "image_process_failure": َشَلَ أَثْنَاءَ مُعَالَجَةِ الصُّورَةِ",
"required_field": "هذا الحقل مطلوب", "required_field": "هَذَا الْحَقْلُ مَطْلُوبٌ",
"displayname_min_length": جب أن يكون اسم العرض على الأقل 3 أحرف", "displayname_min_length": َجِبُ أَنْ يَكُونَ اسْمُ الْعَرْضِ عَلَى الْأَقَلِّ 3 أَحْرُفٍ",
"displayname_max_length": جب أن لا يتجاوز اسم العرض 50 حرفًا", "displayname_max_length": َجِبُ أَنْ يَكُونَ اسْمُ الْعَرْضِ عَلَى الْأَكْثَرِ 50 حَرْفًا",
"report_profile": "الإبلاغ عن هذا الملف", "report_profile": "تَقْرِيرٌ عَنْ هَذَا الْمَلَفِّ الشَّخْصِيِّ",
"report_reason": ماذا تقوم بالإبلاغ عن هذا الملف؟", "report_reason": ِمَاذَا تُقَدِّمُ تَقْرِيرًا عَنْ هَذَا الْمَلَفِّ الشَّخْصِيِّ؟",
"report_description": علومات إضافية", "report_description": َعْلُومَاتٌ إِضَافِيَّةٌ",
"report_description_placeholder": علومات إضافية", "report_description_placeholder": َعْلُومَاتٌ إِضَافِيَّةٌ",
"report": "الإبلاغ", "report": "تَقْرِيرٌ",
"report_reason_hate": طاب كراهية", "report_reason_hate": ِطَابُ الْكُرْهِ",
"report_reason_sexual_content": حتوى جنسي", "report_reason_sexual_content": ُحْتَوًى جِنْسِيٌّ",
"report_reason_violence": نف", "report_reason_violence": ُنْفٌ",
"report_reason_spam": "بريد عشوائي", "report_reason_spam": "رَاسِلَةٌ عَشْوَائِيَّةٌ",
"report_reason_other": "أخرى", "report_reason_other": "آخَرُ",
"profile_reported": م الإبلاغ عن الملف الشخصي", "profile_reported": َمَّ تَقْرِيرُ الْمَلَفِّ الشَّخْصِيِّ",
"your_friend_code": مز الصديق الخاص بك:", "your_friend_code": َمْزُ صَدِيقِكَ:",
"upload_banner": فع بانر", "upload_banner": َفْعُ لَافِتَةٍ",
"uploading_banner": "جاري رفع البانر...", "uploading_banner": َارٍ رَفْعُ اللَّافِتَةِ...",
"background_image_updated": م تحديث صورة الخلفية", "background_image_updated": َمَّ تَحْدِيثُ صُورَةِ الْخَلْفِيَّةِ",
"stats": "الإحصائيات", "stats": "الإحْصَائِيَّاتُ",
"achievements": "الإنجازات", "achievements": "الإِنْجَازَاتُ",
"games": "الألعاب", "games": "الْأَلْعَابُ",
"top_percentile": "الأعلى {{percentile}}%", "top_percentile": "الْأَفْضَلُ {{percentile}}%",
"ranking_updated_weekly": "يتم تحديث التصنيف أسبوعيًا", "ranking_updated_weekly": "التَّرْتِيبُ يُحَدَّثُ أُسْبُوعِيًّا",
"playing": "جاري لعب {{game}}", "playing": َارِي اللَّعِبُ {{game}}",
"achievements_unlocked": "الإنجازات المفتوحة", "achievements_unlocked": "الإِنْجَازَاتُ الْمَفْتُوحَةُ",
"earned_points": "النقاط المكتسبة", "earned_points": "النَّقَاطُ الْمَكْسُوبَةُ",
"show_achievements_on_profile": رض إنجازاتك في ملفك الشخصي", "show_achievements_on_profile": َرْضُ إِنْجَازَاتِكَ عَلَى مَلَفِّكَ الشَّخْصِيِّ",
"show_points_on_profile": رض نقاطك المكتسبة في ملفك الشخصي" "show_points_on_profile": َرْضُ النَّقَاطِ الْمَكْسُوبَةِ عَلَى مَلَفِّكَ الشَّخْصِيِّ"
}, },
"achievement": { "achievement": {
"achievement_unlocked": "تم فتح الإنجاز", "achievement_unlocked": "إِنْجَازٌ مَفْتُوحٌ",
"user_achievements": نجازات {{displayName}}", "user_achievements": ِنْجَازَاتُ {{displayName}}",
"your_achievements": نجازاتك", "your_achievements": ِنْجَازَاتُكَ",
"unlocked_at": م الفتح في: {{date}}", "unlocked_at": َمَّ الْفَتْحُ فِي: {{date}}",
"subscription_needed": حتاج إلى اشتراك Hydra Cloud لعرض هذا المحتوى", "subscription_needed": َحْتَاجُ اشْتِرَاكُ Hydra Cloud لِرُؤْيَةِ هَذَا الْمُحْتَوَى",
"new_achievements_unlocked": م فتح {{achievementCount}} إنجازات جديدة من {{gameCount}} ألعاب", "new_achievements_unlocked": َمَّ فَتْحُ {{achievementCount}} إِنْجَازَاتٍ جَدِيدَةٍ مِنْ {{gameCount}} أَلْعَابٍ",
"achievement_progress": "{{unlockedCount}}/{{totalCount}} إنجازات", "achievement_progress": "{{unlockedCount}}/{{totalCount}} إِنْجَازَاتٍ",
"achievements_unlocked_for_game": م فتح {{achievementCount}} إنجازات جديدة لـ {{gameTitle}}", "achievements_unlocked_for_game": َمَّ فَتْحُ {{achievementCount}} إِنْجَازَاتٍ جَدِيدَةٍ لِـ {{gameTitle}}",
"hidden_achievement_tooltip": "هذا إنجاز مخفي", "hidden_achievement_tooltip": "هَذَا إِنْجَازٌ مَخْفِيٌّ",
"achievement_earn_points": "احصل على {{points}} نقاط مع هذا الإنجاز", "achievement_earn_points": "اكْسِبْ {{points}} نَقَاطًا بِهَذَا الإِنْجَازِ",
"earned_points": "النقاط المكتسبة:", "earned_points": "النَّقَاطُ الْمَكْسُوبَةُ:",
"available_points": "النقاط المتاحة:", "available_points": "النَّقَاطُ الْمُتَوَفِّرَةُ:",
"how_to_earn_achievements_points": يفية كسب نقاط الإنجازات؟" "how_to_earn_achievements_points": َيْفَ تَكْسِبُ نَقَاطَ الإِنْجَازَاتِ؟"
}, },
"hydra_cloud": { "hydra_cloud": {
"subscription_tour_title": "اشتراك Hydra Cloud", "subscription_tour_title": "اشْتِرَاكُ Hydra Cloud",
"subscribe_now": "اشترك الآن", "subscribe_now": "اشْتَرِكِ الْآنَ",
"cloud_saving": فظ سحابي", "cloud_saving": ِفْظٌ سَحَابِيٌّ",
"cloud_achievements": "احفظ إنجازاتك على السحابة", "cloud_achievements": "حِفْظُ إِنْجَازَاتِكَ فِي السَّحَابَةِ",
"animated_profile_picture": "صورة ملف متحركة", "animated_profile_picture": ُورُ الْمَلَفِّ الشَّخْصِيِّ الْمُتَحَرِّكَةِ",
"premium_support": "دعم ممتاز", "premium_support": "الدَّعْمُ الْمُتَقَدِّمُ",
"show_and_compare_achievements": "اعرض وقارن إنجازاتك مع المستخدمين الآخرين", "show_and_compare_achievements": "عَرْضٌ وَمُقَارَنَةُ إِنْجَازَاتِكَ مَعَ مُسْتَخْدِمِينَ آخَرِينَ",
"animated_profile_banner": "بانر ملف متحرك", "animated_profile_banner": "لَافِتَةُ الْمَلَفِّ الشَّخْصِيِّ الْمُتَحَرِّكَةِ",
"hydra_cloud": "Hydra Cloud", "hydra_cloud": "Hydra Cloud",
"hydra_cloud_feature_found": قد اكتشفت ميزة Hydra Cloud!", "hydra_cloud_feature_found": َقَدْ اكْتَشَفْتَ مِيزَةً مِنْ Hydra Cloud!",
"learn_more": "معرفة المزيد" "learn_more": "تَعَلَّمْ أَكْثَرَ"
} }
} }

View File

@@ -14,10 +14,8 @@
"paused": "{{title}} (Спынена)", "paused": "{{title}} (Спынена)",
"downloading": "{{title}} ({{percentage}} - Сцягванне…)", "downloading": "{{title}} ({{percentage}} - Сцягванне…)",
"filter": "Фільтар бібліятэкі", "filter": "Фільтар бібліятэкі",
"home": "Галоўная", "home": "Галоўная"
"favorites": "Улюбленыя"
}, },
"header": { "header": {
"search": "Пошук", "search": "Пошук",
"home": "Галоўная", "home": "Галоўная",

View File

@@ -26,8 +26,7 @@
"game_has_no_executable": "Играта няма избран изпълним файл", "game_has_no_executable": "Играта няма избран изпълним файл",
"sign_in": "Вписване", "sign_in": "Вписване",
"friends": "Приятели", "friends": "Приятели",
"need_help": "Имате нужда от помощ??", "need_help": "Имате нужда от помощ??"
"favorites": "Любими игри"
}, },
"header": { "header": {
"search": "Търсене", "search": "Търсене",
@@ -231,13 +230,13 @@
"behavior": "Поведение", "behavior": "Поведение",
"download_sources": "Източници за изтегляне", "download_sources": "Източници за изтегляне",
"language": "Език", "language": "Език",
"api_token": "API Токен", "real_debrid_api_token": "API Токен",
"enable_real_debrid": "Включи Real-Debrid", "enable_real_debrid": "Включи Real-Debrid",
"real_debrid_description": "Real-Debrid е неограничен даунлоудър, който ви позволява бързо да изтегляте файлове, ограничени само от скоростта на интернет..", "real_debrid_description": "Real-Debrid е неограничен даунлоудър, който ви позволява бързо да изтегляте файлове, ограничени само от скоростта на интернет..",
"debrid_invalid_token": "Невалиден API токен", "real_debrid_invalid_token": "Невалиден API токен",
"debrid_api_token_hint": "Вземете своя API токен <0>тук</0>", "real_debrid_api_token_hint": "Вземете своя API токен <0>тук</0>",
"real_debrid_free_account_error": "Акаунтът \"{{username}}\" е безплатен акаунт. Моля абонирай се за Real-Debrid", "real_debrid_free_account_error": "Акаунтът \"{{username}}\" е безплатен акаунт. Моля абонирай се за Real-Debrid",
"debrid_linked_message": "Акаунтът \"{{username}}\" е свързан", "real_debrid_linked_message": "Акаунтът \"{{username}}\" е свързан",
"save_changes": "Запази промените", "save_changes": "Запази промените",
"changes_saved": "Промените са успешно запазни", "changes_saved": "Промените са успешно запазни",
"download_sources_description": "Hydra ще извлича връзките за изтегляне от тези източници. URL адресът на източника трябва да е директна връзка към .json файл, съдържащ връзките за изтегляне.", "download_sources_description": "Hydra ще извлича връзките за изтегляне от тези източници. URL адресът на източника трябва да е директна връзка към .json файл, съдържащ връзките за изтегляне.",

View File

@@ -20,12 +20,10 @@
"home": "Inici", "home": "Inici",
"queued": "{{title}} (En espera)", "queued": "{{title}} (En espera)",
"game_has_no_executable": "El joc encara no té un executable seleccionat", "game_has_no_executable": "El joc encara no té un executable seleccionat",
"sign_in": "Entra", "sign_in": "Entra"
"favorites": "Favorits"
}, },
"header": { "header": {
"search": "Cerca jocs", "search": "Cerca jocs",
"home": "Inici", "home": "Inici",
"catalogue": "Catàleg", "catalogue": "Catàleg",
"downloads": "Baixades", "downloads": "Baixades",
@@ -163,13 +161,13 @@
"behavior": "Comportament", "behavior": "Comportament",
"download_sources": "Fonts de descàrrega", "download_sources": "Fonts de descàrrega",
"language": "Idioma", "language": "Idioma",
"api_token": "Testimoni API", "real_debrid_api_token": "Testimoni API",
"enable_real_debrid": "Activa el Real Debrid", "enable_real_debrid": "Activa el Real Debrid",
"real_debrid_description": "Real-Debrid és un programa de descàrrega sense restriccions que us permet descarregar fitxers a l'instant i al màxim de la vostra velocitat d'Internet.", "real_debrid_description": "Real-Debrid és un programa de descàrrega sense restriccions que us permet descarregar fitxers a l'instant i al màxim de la vostra velocitat d'Internet.",
"debrid_invalid_token": "Invalida el testimoni de l'API", "real_debrid_invalid_token": "Invalida el testimoni de l'API",
"debrid_api_token_hint": "Pots obtenir la teva clau de l'API <0>aquí</0>.", "real_debrid_api_token_hint": "Pots obtenir la teva clau de l'API <0>aquí</0>.",
"real_debrid_free_account_error": "L'usuari \"{{username}}\" és un compte gratuït. Si us plau subscriu-te a Real-Debrid", "real_debrid_free_account_error": "L'usuari \"{{username}}\" és un compte gratuït. Si us plau subscriu-te a Real-Debrid",
"debrid_linked_message": "Compte \"{{username}}\" vinculat", "real_debrid_linked_message": "Compte \"{{username}}\" vinculat",
"save_changes": "Desa els canvis", "save_changes": "Desa els canvis",
"changes_saved": "Els canvis s'han desat correctament", "changes_saved": "Els canvis s'han desat correctament",
"download_sources_description": "Hydra buscarà els enllaços de descàrrega d'aquestes fonts. L'URL d'origen ha de ser un enllaç directe a un fitxer .json que contingui els enllaços de descàrrega.", "download_sources_description": "Hydra buscarà els enllaços de descàrrega d'aquestes fonts. L'URL d'origen ha de ser un enllaç directe a un fitxer .json que contingui els enllaços de descàrrega.",

View File

@@ -26,8 +26,7 @@
"game_has_no_executable": "Hra nemá zvolen žádný spustitelný soubor", "game_has_no_executable": "Hra nemá zvolen žádný spustitelný soubor",
"sign_in": "Přihlásit se", "sign_in": "Přihlásit se",
"friends": "Přátelé", "friends": "Přátelé",
"need_help": "Potřebujete pomoc?", "need_help": "Potřebujete pomoc?"
"favorites": "Oblíbené"
}, },
"header": { "header": {
"search": "Vyhledat hry", "search": "Vyhledat hry",
@@ -215,13 +214,13 @@
"behavior": "Chování", "behavior": "Chování",
"download_sources": "Zdroje stahování", "download_sources": "Zdroje stahování",
"language": "Jazyk", "language": "Jazyk",
"api_token": "API Token", "real_debrid_api_token": "API Token",
"enable_real_debrid": "Povolit Real-Debrid", "enable_real_debrid": "Povolit Real-Debrid",
"real_debrid_description": "Real-Debrid je neomezený správce stahování, který umožňuje stahovat soubory v nejvyšší rychlosti vašeho internetu.", "real_debrid_description": "Real-Debrid je neomezený správce stahování, který umožňuje stahovat soubory v nejvyšší rychlosti vašeho internetu.",
"debrid_invalid_token": "Neplatný API token", "real_debrid_invalid_token": "Neplatný API token",
"debrid_api_token_hint": "API token můžeš sehnat <0>zde</0>", "real_debrid_api_token_hint": "API token můžeš sehnat <0>zde</0>",
"real_debrid_free_account_error": "Účet \"{{username}}\" má základní úroveň. Prosím předplaťte si Real-Debrid", "real_debrid_free_account_error": "Účet \"{{username}}\" má základní úroveň. Prosím předplaťte si Real-Debrid",
"debrid_linked_message": "Účet \"{{username}}\" je propojen", "real_debrid_linked_message": "Účet \"{{username}}\" je propojen",
"save_changes": "Uložit změny", "save_changes": "Uložit změny",
"changes_saved": "Změny úspěšně uloženy", "changes_saved": "Změny úspěšně uloženy",
"download_sources_description": "Hydra bude odsud sbírat soubory. Zdrojový odkaz musí být .json soubor obsahující odkazy na soubory.", "download_sources_description": "Hydra bude odsud sbírat soubory. Zdrojový odkaz musí být .json soubor obsahující odkazy na soubory.",

View File

@@ -24,12 +24,10 @@
"queued": "{{title}} (I køen)", "queued": "{{title}} (I køen)",
"game_has_no_executable": "Spillet har ikke nogen eksekverbar fil valgt", "game_has_no_executable": "Spillet har ikke nogen eksekverbar fil valgt",
"sign_in": "Log ind", "sign_in": "Log ind",
"friends": "Venner", "friends": "Venner"
"favorites": "Favoritter"
}, },
"header": { "header": {
"search": "Søg efter spil", "search": "Søg efter spil",
"home": "Hjem", "home": "Hjem",
"catalogue": "Katalog", "catalogue": "Katalog",
"downloads": "Downloads", "downloads": "Downloads",
@@ -179,13 +177,13 @@
"behavior": "Opførsel", "behavior": "Opførsel",
"download_sources": "Download kilder", "download_sources": "Download kilder",
"language": "Sprog", "language": "Sprog",
"api_token": "API nøgle", "real_debrid_api_token": "API nøgle",
"enable_real_debrid": "Slå Real-Debrid til", "enable_real_debrid": "Slå Real-Debrid til",
"real_debrid_description": "Real-Debrid er en ubegrænset downloader der gør det muligt for dig at downloade filer med det samme og med den bedste udnyttelse af din internet hastighed.", "real_debrid_description": "Real-Debrid er en ubegrænset downloader der gør det muligt for dig at downloade filer med det samme og med den bedste udnyttelse af din internet hastighed.",
"debrid_invalid_token": "Ugyldig API nøgle", "real_debrid_invalid_token": "Ugyldig API nøgle",
"debrid_api_token_hint": "Du kan få din API nøgle <0>her</0>", "real_debrid_api_token_hint": "Du kan få din API nøgle <0>her</0>",
"real_debrid_free_account_error": "Brugeren \"{{username}}\" er en gratis bruger. Venligst abbonér på Real-Debrid", "real_debrid_free_account_error": "Brugeren \"{{username}}\" er en gratis bruger. Venligst abbonér på Real-Debrid",
"debrid_linked_message": "Brugeren \"{{username}}\" er forbundet", "real_debrid_linked_message": "Brugeren \"{{username}}\" er forbundet",
"save_changes": "Gem ændringer", "save_changes": "Gem ændringer",
"changes_saved": "Ændringer gemt successfuldt", "changes_saved": "Ændringer gemt successfuldt",
"download_sources_description": "Hydra vil hente download links fra disse kilder. Kilde URLen skal være et direkte link til en .json fil der indeholder download linkene.", "download_sources_description": "Hydra vil hente download links fra disse kilder. Kilde URLen skal være et direkte link til en .json fil der indeholder download linkene.",

View File

@@ -20,12 +20,10 @@
"home": "Home", "home": "Home",
"queued": "{{title}} (In Warteschlange)", "queued": "{{title}} (In Warteschlange)",
"game_has_no_executable": "Spiel hat keine ausführbare Datei gewählt", "game_has_no_executable": "Spiel hat keine ausführbare Datei gewählt",
"sign_in": "Anmelden", "sign_in": "Anmelden"
"favorites": "Favoriten"
}, },
"header": { "header": {
"search": "Spiele suchen", "search": "Spiele suchen",
"home": "Home", "home": "Home",
"catalogue": "Katalog", "catalogue": "Katalog",
"downloads": "Downloads", "downloads": "Downloads",
@@ -163,13 +161,13 @@
"behavior": "Verhalten", "behavior": "Verhalten",
"download_sources": "Download-Quellen", "download_sources": "Download-Quellen",
"language": "Sprache", "language": "Sprache",
"api_token": "API Token", "real_debrid_api_token": "API Token",
"enable_real_debrid": "Real-Debrid aktivieren", "enable_real_debrid": "Real-Debrid aktivieren",
"real_debrid_description": "Real-Debrid ist ein unrestriktiver Downloader, der es dir ermöglicht Dateien sofort und mit deiner maximalen Internetgeschwindigkeit herunterzuladen.", "real_debrid_description": "Real-Debrid ist ein unrestriktiver Downloader, der es dir ermöglicht Dateien sofort und mit deiner maximalen Internetgeschwindigkeit herunterzuladen.",
"debrid_invalid_token": "API token nicht gültig", "real_debrid_invalid_token": "API token nicht gültig",
"debrid_api_token_hint": "<0>Hier</0> kannst du dir deinen API Token holen", "real_debrid_api_token_hint": "<0>Hier</0> kannst du dir deinen API Token holen",
"real_debrid_free_account_error": "Das Konto \"{{username}}\" ist ein gratis account. Bitte abonniere Real-Debrid", "real_debrid_free_account_error": "Das Konto \"{{username}}\" ist ein gratis account. Bitte abonniere Real-Debrid",
"debrid_linked_message": "Konto \"{{username}}\" verknüpft", "real_debrid_linked_message": "Konto \"{{username}}\" verknüpft",
"save_changes": "Änderungen speichern", "save_changes": "Änderungen speichern",
"changes_saved": "Änderungen erfolgreich gespeichert", "changes_saved": "Änderungen erfolgreich gespeichert",
"download_sources_description": "Hydra wird die Download-Links von diesen Quellen abrufen. Die Quell-URL muss ein direkter Link zu einer .json Datei, welche die Download-Links enthält, sein.", "download_sources_description": "Hydra wird die Download-Links von diesen Quellen abrufen. Die Quell-URL muss ein direkter Link zu einer .json Datei, welche die Download-Links enthält, sein.",

View File

@@ -26,8 +26,7 @@
"game_has_no_executable": "Game has no executable selected", "game_has_no_executable": "Game has no executable selected",
"sign_in": "Sign in", "sign_in": "Sign in",
"friends": "Friends", "friends": "Friends",
"need_help": "Need help?", "need_help": "Need help?"
"favorites": "Favorites"
}, },
"header": { "header": {
"search": "Search games", "search": "Search games",
@@ -185,13 +184,7 @@
"reset_achievements_description": "This will reset all achievements for {{game}}", "reset_achievements_description": "This will reset all achievements for {{game}}",
"reset_achievements_title": "Are you sure?", "reset_achievements_title": "Are you sure?",
"reset_achievements_success": "Achievements successfully reset", "reset_achievements_success": "Achievements successfully reset",
"reset_achievements_error": "Failed to reset achievements", "reset_achievements_error": "Failed to reset achievements"
"download_error_gofile_quota_exceeded": "You have exceeded your Gofile monthly quota. Please await the quota to reset.",
"download_error_real_debrid_account_not_authorized": "Your Real-Debrid account is not authorized to make new downloads. Please check your account settings and try again.",
"download_error_not_cached_in_real_debrid": "This download is not available on Real-Debrid and polling download status from Real-Debrid is not yet available.",
"download_error_not_cached_in_torbox": "This download is not available on Torbox and polling download status from Torbox is not yet available.",
"game_removed_from_favorites": "Game removed from favorites",
"game_added_to_favorites": "Game added to favorites"
}, },
"activation": { "activation": {
"title": "Activate Hydra", "title": "Activate Hydra",
@@ -243,13 +236,13 @@
"behavior": "Behavior", "behavior": "Behavior",
"download_sources": "Download sources", "download_sources": "Download sources",
"language": "Language", "language": "Language",
"api_token": "API Token", "real_debrid_api_token": "API Token",
"enable_real_debrid": "Enable Real-Debrid", "enable_real_debrid": "Enable Real-Debrid",
"real_debrid_description": "Real-Debrid is an unrestricted downloader that allows you to quickly download files, only limited by your internet speed.", "real_debrid_description": "Real-Debrid is an unrestricted downloader that allows you to quickly download files, only limited by your internet speed.",
"debrid_invalid_token": "Invalid API token", "real_debrid_invalid_token": "Invalid API token",
"debrid_api_token_hint": "You can get your API token <0>here</0>", "real_debrid_api_token_hint": "You can get your API token <0>here</0>",
"real_debrid_free_account_error": "The account \"{{username}}\" is a free account. Please subscribe to Real-Debrid", "real_debrid_free_account_error": "The account \"{{username}}\" is a free account. Please subscribe to Real-Debrid",
"debrid_linked_message": "Account \"{{username}}\" linked", "real_debrid_linked_message": "Account \"{{username}}\" linked",
"save_changes": "Save changes", "save_changes": "Save changes",
"changes_saved": "Changes successfully saved", "changes_saved": "Changes successfully saved",
"download_sources_description": "Hydra will fetch the download links from these sources. The source URL must be a direct link to a .json file containing the download links.", "download_sources_description": "Hydra will fetch the download links from these sources. The source URL must be a direct link to a .json file containing the download links.",
@@ -303,36 +296,7 @@
"become_subscriber": "Be Hydra Cloud", "become_subscriber": "Be Hydra Cloud",
"subscription_renew_cancelled": "Automatic renewal is disabled", "subscription_renew_cancelled": "Automatic renewal is disabled",
"subscription_renews_on": "Your subscription renews on {{date}}", "subscription_renews_on": "Your subscription renews on {{date}}",
"bill_sent_until": "Your next bill will be sent until this day", "bill_sent_until": "Your next bill will be sent until this day"
"no_themes": "Seems like you don't have any themes yet, but no worries, click here to create your first masterpiece.",
"editor_tab_code": "Code",
"editor_tab_info": "Info",
"editor_tab_save": "Save",
"web_store": "Web store",
"clear_themes": "Clear",
"create_theme": "Create",
"create_theme_modal_title": "Create custom theme",
"create_theme_modal_description": "Create a new theme to customize Hydra's appearance",
"theme_name": "Name",
"insert_theme_name": "Insert theme name",
"set_theme": "Set theme",
"unset_theme": "Unset theme",
"delete_theme": "Delete theme",
"edit_theme": "Edit theme",
"delete_all_themes": "Delete all themes",
"delete_all_themes_description": "This will delete all your custom themes",
"delete_theme_description": "This will delete the theme {{theme}}",
"cancel": "Cancel",
"appearance": "Appearance",
"enable_torbox": "Enable Torbox",
"torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.",
"torbox_account_linked": "TorBox account linked",
"real_debrid_account_linked": "Real-Debrid account linked",
"name_min_length": "Theme name must be at least 3 characters long",
"import_theme": "Import theme",
"import_theme_description": "You will import {{theme}} from the theme store",
"error_importing_theme": "Error importing theme",
"theme_imported": "Theme imported successfully"
}, },
"notifications": { "notifications": {
"download_complete": "Download complete", "download_complete": "Download complete",
@@ -444,9 +408,6 @@
"show_achievements_on_profile": "Show your achievements on your profile", "show_achievements_on_profile": "Show your achievements on your profile",
"show_points_on_profile": "Show your earned points on your profile" "show_points_on_profile": "Show your earned points on your profile"
}, },
"badge": {
"badge_description_theme_creator": "Awarded to those who created a custom theme"
},
"achievement": { "achievement": {
"achievement_unlocked": "Achievement unlocked", "achievement_unlocked": "Achievement unlocked",
"user_achievements": "{{displayName}}'s Achievements", "user_achievements": "{{displayName}}'s Achievements",

View File

@@ -26,8 +26,7 @@
"game_has_no_executable": "El juego no tiene un ejecutable seleccionado", "game_has_no_executable": "El juego no tiene un ejecutable seleccionado",
"sign_in": "Iniciar sesión", "sign_in": "Iniciar sesión",
"friends": "Amigos", "friends": "Amigos",
"need_help": "¿Necesitas ayuda?", "need_help": "¿Necesitas ayuda?"
"favorites": "Favoritos"
}, },
"header": { "header": {
"search": "Buscar juegos", "search": "Buscar juegos",
@@ -176,16 +175,7 @@
"backup_from": "Copia de seguridad de {{date}}", "backup_from": "Copia de seguridad de {{date}}",
"custom_backup_location_set": "Se configuró la carpeta de copia de seguridad", "custom_backup_location_set": "Se configuró la carpeta de copia de seguridad",
"clear": "Limpiar", "clear": "Limpiar",
"no_directory_selected": "No se seleccionó un directorio", "no_directory_selected": "No se seleccionó un directorio"
"launch_options": "Opciones de Inicio",
"launch_options_description": "Los usuarios avanzados pueden introducir sus propias modificaciones de opciones de inicio (característica experimental)",
"launch_options_placeholder": "Sin parámetro específicado",
"no_write_permission": "No se puede descargar en este directorio. Presiona aquí para aprender más.",
"reset_achievements": "Reiniciar logros",
"reset_achievements_description": "Esto reiniciará todos los logros de {{game}}",
"reset_achievements_title": "¿Estás seguro?",
"reset_achievements_success": "Logros reiniciados exitosamente",
"reset_achievements_error": "Se produjo un error al reiniciar los logros"
}, },
"activation": { "activation": {
"title": "Activar Hydra", "title": "Activar Hydra",
@@ -237,13 +227,13 @@
"behavior": "Otros", "behavior": "Otros",
"download_sources": "Fuentes de descarga", "download_sources": "Fuentes de descarga",
"language": "Idioma", "language": "Idioma",
"api_token": "Token API", "real_debrid_api_token": "Token API",
"enable_real_debrid": "Activar Real-Debrid", "enable_real_debrid": "Activar Real-Debrid",
"real_debrid_description": "Real-Debrid es una forma de descargar sin restricciones archivos instantáneamente con la máxima velocidad de tu internet.", "real_debrid_description": "Real-Debrid es una forma de descargar sin restricciones archivos instantáneamente con la máxima velocidad de tu internet.",
"debrid_invalid_token": "Token de API inválido", "real_debrid_invalid_token": "Token de API inválido",
"debrid_api_token_hint": "Puedes obtener tu clave de API <0>aquí</0>", "real_debrid_api_token_hint": "Puedes obtener tu clave de API <0>aquí</0>",
"real_debrid_free_account_error": "La cuenta \"{{username}}\" es una cuenta gratuita. Por favor, suscríbete a Real-Debrid", "real_debrid_free_account_error": "La cuenta \"{{username}}\" es una cuenta gratuita. Por favor, suscríbete a Real-Debrid",
"debrid_linked_message": "Cuenta \"{{username}}\" vinculada", "real_debrid_linked_message": "Cuenta \"{{username}}\" vinculada",
"save_changes": "Guardar cambios", "save_changes": "Guardar cambios",
"changes_saved": "Ajustes guardados exitosamente", "changes_saved": "Ajustes guardados exitosamente",
"download_sources_description": "Hydra buscará los enlaces de descarga de estas fuentes. La URL de origen debe ser un enlace directo a un archivo .json que contenga los enlaces de descarga", "download_sources_description": "Hydra buscará los enlaces de descarga de estas fuentes. La URL de origen debe ser un enlace directo a un archivo .json que contenga los enlaces de descarga",
@@ -281,23 +271,7 @@
"launch_minimized": "Iniciar Hydra minimizado", "launch_minimized": "Iniciar Hydra minimizado",
"disable_nsfw_alert": "Desactivar alerta NSFW", "disable_nsfw_alert": "Desactivar alerta NSFW",
"seed_after_download_complete": "Realizar seeding después de que se completa la descarga", "seed_after_download_complete": "Realizar seeding después de que se completa la descarga",
"show_hidden_achievement_description": "Ocultar descripción de logros ocultos antes de desbloquearlos", "show_hidden_achievement_description": "Ocultar descripción de logros ocultos antes de desbloquearlos"
"account": "Cuenta",
"account_data_updated_successfully": "Datos de la cuenta actualizados",
"bill_sent_until": "Tú próxima factura se enviará el {{date}}",
"current_email": "Correo actual:",
"manage_subscription": "Gestionar suscripción",
"no_email_account": "No has configurado un correo aún",
"no_subscription": "Disfruta Hydra de la mejor manera",
"no_users_blocked": "No tienes usuarios bloqueados",
"notifications": "Notificaciones",
"renew_subscription": "Renovar Hydra Cloud",
"subscription_active_until": "Tu Hydra Cloud está activa hasta {{date}}",
"subscription_expired_at": "Tú suscripción expiró el {{date}}",
"subscription_renew_cancelled": "Está desactivada la renovación automática",
"subscription_renews_on": "Tú suscripción se renueva el {{date}}",
"update_email": "Actualizar correo",
"update_password": "Actualizar contraseña"
}, },
"notifications": { "notifications": {
"download_complete": "Descarga completada", "download_complete": "Descarga completada",

View File

@@ -25,8 +25,7 @@
"queued": "{{title}} (Järjekorras)", "queued": "{{title}} (Järjekorras)",
"game_has_no_executable": "Mängul pole käivitusfaili valitud", "game_has_no_executable": "Mängul pole käivitusfaili valitud",
"sign_in": "Logi sisse", "sign_in": "Logi sisse",
"friends": "Sõbrad", "friends": "Sõbrad"
"favorites": "Lemmikud"
}, },
"header": { "header": {
"search": "Otsi mänge", "search": "Otsi mänge",
@@ -214,13 +213,13 @@
"behavior": "Käitumine", "behavior": "Käitumine",
"download_sources": "Allalaadimise allikad", "download_sources": "Allalaadimise allikad",
"language": "Keel", "language": "Keel",
"api_token": "API Võti", "real_debrid_api_token": "API Võti",
"enable_real_debrid": "Luba Real-Debrid", "enable_real_debrid": "Luba Real-Debrid",
"real_debrid_description": "Real-Debrid on piiranguteta allalaadija, mis võimaldab sul faile alla laadida koheselt ja sinu internetiühenduse parima kiirusega.", "real_debrid_description": "Real-Debrid on piiranguteta allalaadija, mis võimaldab sul faile alla laadida koheselt ja sinu internetiühenduse parima kiirusega.",
"debrid_invalid_token": "Vigane API võti", "real_debrid_invalid_token": "Vigane API võti",
"debrid_api_token_hint": "Sa saad oma API võtme <0>siit</0>", "real_debrid_api_token_hint": "Sa saad oma API võtme <0>siit</0>",
"real_debrid_free_account_error": "Konto \"{{username}}\" on tasuta konto. Palun telli Real-Debrid", "real_debrid_free_account_error": "Konto \"{{username}}\" on tasuta konto. Palun telli Real-Debrid",
"debrid_linked_message": "Konto \"{{username}}\" ühendatud", "real_debrid_linked_message": "Konto \"{{username}}\" ühendatud",
"save_changes": "Salvesta muudatused", "save_changes": "Salvesta muudatused",
"changes_saved": "Muudatused edukalt salvestatud", "changes_saved": "Muudatused edukalt salvestatud",
"download_sources_description": "Hydra laeb allalaadimise lingid nendest allikatest. Allika URL peab olema otsene link .json failile, mis sisaldab allalaadimise linke.", "download_sources_description": "Hydra laeb allalaadimise lingid nendest allikatest. Allika URL peab olema otsene link .json failile, mis sisaldab allalaadimise linke.",

View File

@@ -14,10 +14,8 @@
"paused": "{{title}} (متوقف شده)", "paused": "{{title}} (متوقف شده)",
"downloading": "{{title}} ({{percentage}} - در حال دانلود…)", "downloading": "{{title}} ({{percentage}} - در حال دانلود…)",
"filter": "فیلتر کردن کتابخانه", "filter": "فیلتر کردن کتابخانه",
"home": "خانه", "home": "خانه"
"favorites": "علاقه‌مندی‌ها"
}, },
"header": { "header": {
"search": "جستجوی بازی‌ها", "search": "جستجوی بازی‌ها",
"home": "خانه", "home": "خانه",
@@ -112,7 +110,7 @@
"general": "کلی", "general": "کلی",
"behavior": "رفتار", "behavior": "رفتار",
"enable_real_debrid": "فعال‌سازی Real-Debrid", "enable_real_debrid": "فعال‌سازی Real-Debrid",
"debrid_api_token_hint": "کلید API خود را از <ب0>اینجا</0> بگیرید.", "real_debrid_api_token_hint": "کلید API خود را از <ب0>اینجا</0> بگیرید.",
"save_changes": "ذخیره تغییرات" "save_changes": "ذخیره تغییرات"
}, },
"notifications": { "notifications": {

View File

@@ -14,12 +14,10 @@
"paused": "{{title}} (En pause)", "paused": "{{title}} (En pause)",
"downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)", "downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)",
"filter": "Filtrer la bibliothèque", "filter": "Filtrer la bibliothèque",
"home": "Page daccueil", "home": "Page daccueil"
"favorites": "Favoris"
}, },
"header": { "header": {
"search": "Recherche", "search": "Recherche",
"catalogue": "Catalogue", "catalogue": "Catalogue",
"downloads": "Téléchargements", "downloads": "Téléchargements",
"search_results": "Résultats de la recherche", "search_results": "Résultats de la recherche",

View File

@@ -14,12 +14,10 @@
"paused": "{{title}} (Szünet)", "paused": "{{title}} (Szünet)",
"downloading": "{{title}} ({{percentage}} - Letöltés…)", "downloading": "{{title}} ({{percentage}} - Letöltés…)",
"filter": "Könyvtár szűrése", "filter": "Könyvtár szűrése",
"home": "Főoldal", "home": "Főoldal"
"favorites": "Kedvenc játékok"
}, },
"header": { "header": {
"search": "Keresés", "search": "Keresés",
"home": "Főoldal", "home": "Főoldal",
"catalogue": "Katalógus", "catalogue": "Katalógus",
"downloads": "Letöltések", "downloads": "Letöltések",

View File

@@ -20,12 +20,10 @@
"home": "Beranda", "home": "Beranda",
"queued": "{{title}} (Antrian)", "queued": "{{title}} (Antrian)",
"game_has_no_executable": "Game tidak punya file eksekusi yang dipilih", "game_has_no_executable": "Game tidak punya file eksekusi yang dipilih",
"sign_in": "Masuk", "sign_in": "Masuk"
"favorites": "Favorit"
}, },
"header": { "header": {
"search": "Cari game", "search": "Cari game",
"home": "Beranda", "home": "Beranda",
"catalogue": "Katalog", "catalogue": "Katalog",
"downloads": "Unduhan", "downloads": "Unduhan",
@@ -163,13 +161,13 @@
"behavior": "Perilaku", "behavior": "Perilaku",
"download_sources": "Sumber unduhan", "download_sources": "Sumber unduhan",
"language": "Bahasa", "language": "Bahasa",
"api_token": "Token API", "real_debrid_api_token": "Token API",
"enable_real_debrid": "Aktifkan Real-Debrid", "enable_real_debrid": "Aktifkan Real-Debrid",
"real_debrid_description": "Real-Debrid adalah downloader tanpa batas yang memungkinkan kamu untuk mengunduh file dengan cepat dan pada kecepatan terbaik dari Internet kamu.", "real_debrid_description": "Real-Debrid adalah downloader tanpa batas yang memungkinkan kamu untuk mengunduh file dengan cepat dan pada kecepatan terbaik dari Internet kamu.",
"debrid_invalid_token": "Token API tidak valid", "real_debrid_invalid_token": "Token API tidak valid",
"debrid_api_token_hint": "Kamu bisa dapatkan token API di <0>sini</0>", "real_debrid_api_token_hint": "Kamu bisa dapatkan token API di <0>sini</0>",
"real_debrid_free_account_error": "Akun \"{{username}}\" adalah akun gratis. Silakan berlangganan Real-Debrid", "real_debrid_free_account_error": "Akun \"{{username}}\" adalah akun gratis. Silakan berlangganan Real-Debrid",
"debrid_linked_message": "Akun \"{{username}}\" terhubung", "real_debrid_linked_message": "Akun \"{{username}}\" terhubung",
"save_changes": "Simpan perubahan", "save_changes": "Simpan perubahan",
"changes_saved": "Perubahan disimpan berhasil", "changes_saved": "Perubahan disimpan berhasil",
"download_sources_description": "Hydra akan mencari link unduhan dari sini. URL harus menuju file .json dengan link unduhan.", "download_sources_description": "Hydra akan mencari link unduhan dari sini. URL harus menuju file .json dengan link unduhan.",

View File

@@ -14,12 +14,10 @@
"paused": "{{title}} (In pausa)", "paused": "{{title}} (In pausa)",
"downloading": "{{title}} ({{percentage}} - Download…)", "downloading": "{{title}} ({{percentage}} - Download…)",
"filter": "Filtra libreria", "filter": "Filtra libreria",
"home": "Home", "home": "Home"
"favorites": "Preferiti"
}, },
"header": { "header": {
"search": "Cerca", "search": "Cerca",
"home": "Home", "home": "Home",
"catalogue": "Catalogo", "catalogue": "Catalogo",
"downloads": "Download", "downloads": "Download",
@@ -120,7 +118,7 @@
"general": "Generale", "general": "Generale",
"behavior": "Comportamento", "behavior": "Comportamento",
"enable_real_debrid": "Abilita Real Debrid", "enable_real_debrid": "Abilita Real Debrid",
"debrid_api_token_hint": "Puoi trovare la tua chiave API <0>here</0>", "real_debrid_api_token_hint": "Puoi trovare la tua chiave API <0>here</0>",
"save_changes": "Salva modifiche" "save_changes": "Salva modifiche"
}, },
"notifications": { "notifications": {

View File

@@ -20,10 +20,8 @@
"home": "Басты бет", "home": "Басты бет",
"queued": "{{title}} (Кезекте)", "queued": "{{title}} (Кезекте)",
"game_has_no_executable": "Ойынды іске қосу файлы таңдалмаған", "game_has_no_executable": "Ойынды іске қосу файлы таңдалмаған",
"sign_in": "Кіру", "sign_in": "Кіру"
"favorites": "Таңдаулылар"
}, },
"header": { "header": {
"search": "Іздеу", "search": "Іздеу",
"home": "Басты бет", "home": "Басты бет",
@@ -161,13 +159,13 @@
"behavior": "Мінез-құлық", "behavior": "Мінез-құлық",
"download_sources": "Жүктеу көздері", "download_sources": "Жүктеу көздері",
"language": "Тіл", "language": "Тіл",
"api_token": "API Кілті", "real_debrid_api_token": "API Кілті",
"enable_real_debrid": "Real-Debrid-ті қосу", "enable_real_debrid": "Real-Debrid-ті қосу",
"real_debrid_description": "Real-Debrid - бұл шектеусіз жүктеуші, ол интернетте орналастырылған файлдарды тез жүктеуге немесе жеке желі арқылы кез келген блоктарды айналып өтіп, оларды бірден плеерге беруге мүмкіндік береді.", "real_debrid_description": "Real-Debrid - бұл шектеусіз жүктеуші, ол интернетте орналастырылған файлдарды тез жүктеуге немесе жеке желі арқылы кез келген блоктарды айналып өтіп, оларды бірден плеерге беруге мүмкіндік береді.",
"debrid_invalid_token": "Қате API кілті", "real_debrid_invalid_token": "Қате API кілті",
"debrid_api_token_hint": "API кілтін <0>осы жерден</0> алуға болады", "real_debrid_api_token_hint": "API кілтін <0>осы жерден</0> алуға болады",
"real_debrid_free_account_error": "\"{{username}}\" аккаунты жазылымға ие емес. Real-Debrid жазылымын алыңыз", "real_debrid_free_account_error": "\"{{username}}\" аккаунты жазылымға ие емес. Real-Debrid жазылымын алыңыз",
"debrid_linked_message": "\"{{username}}\" аккаунты байланған", "real_debrid_linked_message": "\"{{username}}\" аккаунты байланған",
"save_changes": "Өзгерістерді сақтау", "save_changes": "Өзгерістерді сақтау",
"changes_saved": "Өзгерістер сәтті сақталды", "changes_saved": "Өзгерістер сәтті сақталды",
"download_sources_description": "Hydra осы көздерден жүктеу сілтемелерін алады. URL-да жүктеу сілтемелері бар .json файлына тікелей сілтеме болуы керек.", "download_sources_description": "Hydra осы көздерден жүктеу сілтемелерін алады. URL-да жүктеу сілтемелері бар .json файлына тікелей сілтеме болуы керек.",

View File

@@ -14,10 +14,8 @@
"paused": "{{title}} (일시 정지됨)", "paused": "{{title}} (일시 정지됨)",
"downloading": "{{title}} ({{percentage}} - 다운로드 중…)", "downloading": "{{title}} ({{percentage}} - 다운로드 중…)",
"filter": "라이브러리 정렬", "filter": "라이브러리 정렬",
"home": "홈", "home": "홈"
"favorites": "즐겨찾기"
}, },
"header": { "header": {
"search": "게임 검색하기", "search": "게임 검색하기",
"home": "홈", "home": "홈",
@@ -112,7 +110,7 @@
"general": "일반", "general": "일반",
"behavior": "행동", "behavior": "행동",
"enable_real_debrid": "Real-Debrid 활성화", "enable_real_debrid": "Real-Debrid 활성화",
"debrid_api_token_hint": "API 키를 <0>이곳</0>에서 얻으세요.", "real_debrid_api_token_hint": "API 키를 <0>이곳</0>에서 얻으세요.",
"save_changes": "변경 사항 저장" "save_changes": "변경 사항 저장"
}, },
"notifications": { "notifications": {

View File

@@ -24,12 +24,10 @@
"queued": "{{title}} (I køen)", "queued": "{{title}} (I køen)",
"game_has_no_executable": "Spillet har ikke noen kjørbar fil valgt", "game_has_no_executable": "Spillet har ikke noen kjørbar fil valgt",
"sign_in": "Logge inn", "sign_in": "Logge inn",
"friends": "Venner", "friends": "Venner"
"favorites": "Favoritter"
}, },
"header": { "header": {
"search": "Søk efter spill", "search": "Søk efter spill",
"home": "Hjem", "home": "Hjem",
"catalogue": "Katalog", "catalogue": "Katalog",
"downloads": "Nedlastinger", "downloads": "Nedlastinger",
@@ -179,13 +177,13 @@
"behavior": "Oppførsel", "behavior": "Oppførsel",
"download_sources": "Nedlastingskilder", "download_sources": "Nedlastingskilder",
"language": "Språk", "language": "Språk",
"api_token": "API nøkkel", "real_debrid_api_token": "API nøkkel",
"enable_real_debrid": "Slå på Real-Debrid", "enable_real_debrid": "Slå på Real-Debrid",
"real_debrid_description": "Real-Debrid er en ubegrenset nedlaster som gør det mulig for deg å laste ned filer med en gang og med den beste utnyttelsen av internethastigheten din.", "real_debrid_description": "Real-Debrid er en ubegrenset nedlaster som gør det mulig for deg å laste ned filer med en gang og med den beste utnyttelsen av internethastigheten din.",
"debrid_invalid_token": "Ugyldig API nøkkel", "real_debrid_invalid_token": "Ugyldig API nøkkel",
"debrid_api_token_hint": "Du kan få API nøkkelen din <0>her</0>", "real_debrid_api_token_hint": "Du kan få API nøkkelen din <0>her</0>",
"real_debrid_free_account_error": "Brukeren \"{{username}}\" er en gratis bruker. Vennligst abboner på Real-Debrid", "real_debrid_free_account_error": "Brukeren \"{{username}}\" er en gratis bruker. Vennligst abboner på Real-Debrid",
"debrid_linked_message": "Brukeren \"{{username}}\" er forbunnet", "real_debrid_linked_message": "Brukeren \"{{username}}\" er forbunnet",
"save_changes": "Lagre endringer", "save_changes": "Lagre endringer",
"changes_saved": "Lagring av endringer vellykket", "changes_saved": "Lagring av endringer vellykket",
"download_sources_description": "Hydra vil hente nedlastingslenker fra disse kildene. Kilde URLen skal være en direkte lenke til en .json fil som inneholder nedlastingslenkene.", "download_sources_description": "Hydra vil hente nedlastingslenker fra disse kildene. Kilde URLen skal være en direkte lenke til en .json fil som inneholder nedlastingslenkene.",

View File

@@ -14,12 +14,10 @@
"paused": "{{title}} (Gepauzeerd)", "paused": "{{title}} (Gepauzeerd)",
"downloading": "{{title}} ({{percentage}} - Downloading…)", "downloading": "{{title}} ({{percentage}} - Downloading…)",
"filter": "Filter Bibliotheek", "filter": "Filter Bibliotheek",
"home": "Home", "home": "Home"
"favorites": "Favorieten"
}, },
"header": { "header": {
"search": "Zoek spellen", "search": "Zoek spellen",
"home": "Home", "home": "Home",
"catalogue": "Bibliotheek", "catalogue": "Bibliotheek",
"downloads": "Downloads", "downloads": "Downloads",
@@ -113,7 +111,7 @@
"general": "Algemeen", "general": "Algemeen",
"behavior": "Gedrag", "behavior": "Gedrag",
"enable_real_debrid": "Enable Real-Debrid", "enable_real_debrid": "Enable Real-Debrid",
"debrid_api_token_hint": "U kunt uw API-sleutel <0>hier</0> verkrijgen.", "real_debrid_api_token_hint": "U kunt uw API-sleutel <0>hier</0> verkrijgen.",
"save_changes": "Wijzigingen opslaan" "save_changes": "Wijzigingen opslaan"
}, },
"notifications": { "notifications": {

View File

@@ -14,12 +14,10 @@
"paused": "{{title}} (Zatrzymano)", "paused": "{{title}} (Zatrzymano)",
"downloading": "{{title}} ({{percentage}} - Pobieranie…)", "downloading": "{{title}} ({{percentage}} - Pobieranie…)",
"filter": "Filtruj biblioteke", "filter": "Filtruj biblioteke",
"home": "Główna", "home": "Główna"
"favorites": "Ulubione"
}, },
"header": { "header": {
"search": "Szukaj", "search": "Szukaj",
"home": "Główna", "home": "Główna",
"catalogue": "Katalog", "catalogue": "Katalog",
"downloads": "Pobrane", "downloads": "Pobrane",
@@ -121,7 +119,7 @@
"behavior": "Zachowania", "behavior": "Zachowania",
"language": "Język", "language": "Język",
"enable_real_debrid": "Włącz Real-Debrid", "enable_real_debrid": "Włącz Real-Debrid",
"debrid_api_token_hint": "Możesz uzyskać swój klucz API <0>tutaj</0>", "real_debrid_api_token_hint": "Możesz uzyskać swój klucz API <0>tutaj</0>",
"save_changes": "Zapisz zmiany" "save_changes": "Zapisz zmiany"
}, },
"notifications": { "notifications": {

View File

@@ -7,7 +7,7 @@
"featured": "Destaques", "featured": "Destaques",
"hot": "Populares", "hot": "Populares",
"weekly": "📅 Mais baixados da semana", "weekly": "📅 Mais baixados da semana",
"achievements": "🏆 Pra platinar", "achievements": "🏆 Para platinar",
"surprise_me": "Surpreenda-me", "surprise_me": "Surpreenda-me",
"no_results": "Nenhum resultado encontrado", "no_results": "Nenhum resultado encontrado",
"start_typing": "Comece a digitar para pesquisar…" "start_typing": "Comece a digitar para pesquisar…"
@@ -26,12 +26,10 @@
"game_has_no_executable": "Jogo não possui executável selecionado", "game_has_no_executable": "Jogo não possui executável selecionado",
"sign_in": "Login", "sign_in": "Login",
"friends": "Amigos", "friends": "Amigos",
"need_help": "Precisa de ajuda?", "need_help": "Precisa de ajuda?"
"favorites": "Favoritos"
}, },
"header": { "header": {
"search": "Buscar jogos", "search": "Buscar jogos",
"catalogue": "Catálogo", "catalogue": "Catálogo",
"downloads": "Downloads", "downloads": "Downloads",
"search_results": "Resultados da busca", "search_results": "Resultados da busca",
@@ -174,14 +172,7 @@
"reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}", "reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}",
"reset_achievements_title": "Tem certeza?", "reset_achievements_title": "Tem certeza?",
"reset_achievements_success": "Conquistas resetadas com sucesso", "reset_achievements_success": "Conquistas resetadas com sucesso",
"reset_achievements_error": "Falha ao resetar conquistas", "reset_achievements_error": "Falha ao resetar conquistas"
"no_write_permission": "Não é possível baixar nesse diretório. Clique aqui para saber mais.",
"download_error_gofile_quota_exceeded": "Você excedeu sua cota mensal do Gofile. Por favor, aguarde a cota resetar.",
"download_error_real_debrid_account_not_authorized": "Sua conta do Real-Debrid não está autorizada a fazer novos downloads. Por favor, verifique sua assinatura e tente novamente.",
"download_error_not_cached_in_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.",
"download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível.",
"game_removed_from_favorites": "Jogo removido dos favoritos",
"game_added_to_favorites": "Jogo adicionado aos favoritos"
}, },
"activation": { "activation": {
"title": "Ativação", "title": "Ativação",
@@ -233,13 +224,13 @@
"behavior": "Comportamento", "behavior": "Comportamento",
"download_sources": "Fontes de download", "download_sources": "Fontes de download",
"language": "Idioma", "language": "Idioma",
"api_token": "Token de API", "real_debrid_api_token": "Token de API",
"enable_real_debrid": "Habilitar Real-Debrid", "enable_real_debrid": "Habilitar Real-Debrid",
"debrid_api_token_hint": "Você pode obter seu token de API <0>aqui</0>", "real_debrid_api_token_hint": "Você pode obter seu token de API <0>aqui</0>",
"real_debrid_description": "O Real-Debrid é um downloader sem restrições que permite baixar arquivos instantaneamente e com a melhor velocidade da sua Internet.", "real_debrid_description": "O Real-Debrid é um downloader sem restrições que permite baixar arquivos instantaneamente e com a melhor velocidade da sua Internet.",
"debrid_invalid_token": "Token de API inválido", "real_debrid_invalid_token": "Token de API inválido",
"real_debrid_free_account_error": "A conta \"{{username}}\" é uma conta gratuita. Por favor, assine a Real-Debrid", "real_debrid_free_account_error": "A conta \"{{username}}\" é uma conta gratuita. Por favor, assine a Real-Debrid",
"debrid_linked_message": "Conta \"{{username}}\" vinculada", "real_debrid_linked_message": "Conta \"{{username}}\" vinculada",
"save_changes": "Salvar mudanças", "save_changes": "Salvar mudanças",
"changes_saved": "Ajustes salvos com sucesso", "changes_saved": "Ajustes salvos com sucesso",
"download_sources_description": "Hydra vai buscar links de download em todas as fontes habilitadas. A URL da fonte deve ser um link direto para um arquivo .json contendo uma lista de links.", "download_sources_description": "Hydra vai buscar links de download em todas as fontes habilitadas. A URL da fonte deve ser um link direto para um arquivo .json contendo uma lista de links.",
@@ -293,34 +284,7 @@
"become_subscriber": "Seja Hydra Cloud", "become_subscriber": "Seja Hydra Cloud",
"subscription_renew_cancelled": "A renovação automática está desativada", "subscription_renew_cancelled": "A renovação automática está desativada",
"subscription_renews_on": "Sua assinatura renova dia {{date}}", "subscription_renews_on": "Sua assinatura renova dia {{date}}",
"bill_sent_until": "Sua próxima cobrança será enviada até esse dia", "bill_sent_until": "Sua próxima cobrança será enviada até esse dia"
"no_themes": "Parece que você ainda não tem nenhum tema. Não se preocupe, clique aqui para criar sua primeira obra de arte.",
"editor_tab_save": "Salvar",
"web_store": "Loja de temas",
"clear_themes": "Limpar",
"create_theme": "Criar",
"create_theme_modal_title": "Criar tema customizado",
"create_theme_modal_description": "Criar novo tema para customizar a aparência do Hydra",
"theme_name": "Nome",
"insert_theme_name": "Insira o nome do tema",
"set_theme": "Habilitar tema",
"unset_theme": "Desabilitar tema",
"delete_theme": "Deletar tema",
"edit_theme": "Editar tema",
"delete_all_themes": "Deletar todos os temas",
"delete_all_themes_description": "Isso irá deletar todos os seus temas",
"delete_theme_description": "Isso irá deletar o tema {{theme}}",
"cancel": "Cancelar",
"appearance": "Aparência",
"enable_torbox": "Habilitar Torbox",
"torbox_description": "TorBox é o seu serviço de seedbox premium que rivaliza até com os melhores servidores do mercado.",
"torbox_account_linked": "Conta do TorBox vinculada",
"real_debrid_account_linked": "Conta Real-Debrid associada",
"name_min_length": "O nome do tema deve ter pelo menos 3 caracteres",
"import_theme": "Importar tema",
"import_theme_description": "Você irá importar {{theme}} da loja de temas",
"error_importing_theme": "Erro ao importar tema",
"theme_imported": "Tema importado com sucesso"
}, },
"notifications": { "notifications": {
"download_complete": "Download concluído", "download_complete": "Download concluído",
@@ -440,9 +404,6 @@
"show_achievements_on_profile": "Exiba suas conquistas no perfil", "show_achievements_on_profile": "Exiba suas conquistas no perfil",
"show_points_on_profile": "Exiba seus pontos ganhos no perfil" "show_points_on_profile": "Exiba seus pontos ganhos no perfil"
}, },
"badge": {
"badge_description_theme_creator": "Concedido àqueles que criaram um tema customizado"
},
"achievement": { "achievement": {
"achievement_unlocked": "Conquista desbloqueada", "achievement_unlocked": "Conquista desbloqueada",
"your_achievements": "Suas Conquistas", "your_achievements": "Suas Conquistas",

View File

@@ -25,12 +25,10 @@
"queued": "{{title}} (Na fila)", "queued": "{{title}} (Na fila)",
"game_has_no_executable": "O jogo não tem um executável selecionado", "game_has_no_executable": "O jogo não tem um executável selecionado",
"sign_in": "Iniciar sessão", "sign_in": "Iniciar sessão",
"friends": "Amigos", "friends": "Amigos"
"favorites": "Favoritos"
}, },
"header": { "header": {
"search": "Procurar jogos", "search": "Procurar jogos",
"catalogue": "Catálogo", "catalogue": "Catálogo",
"downloads": "Transferências", "downloads": "Transferências",
"search_results": "Resultados da pesquisa", "search_results": "Resultados da pesquisa",
@@ -207,13 +205,13 @@
"behavior": "Comportamento", "behavior": "Comportamento",
"download_sources": "Fontes de transferência", "download_sources": "Fontes de transferência",
"language": "Idioma", "language": "Idioma",
"api_token": "Token de API", "real_debrid_api_token": "Token de API",
"enable_real_debrid": "Ativar Real-Debrid", "enable_real_debrid": "Ativar Real-Debrid",
"debrid_api_token_hint": "Podes obter o teu token de API <0>aqui</0>", "real_debrid_api_token_hint": "Podes obter o teu token de API <0>aqui</0>",
"real_debrid_description": "O Real-Debrid é um downloader sem restrições que permite descarregar ficheiros instantaneamente e com a melhor velocidade da tua Internet.", "real_debrid_description": "O Real-Debrid é um downloader sem restrições que permite descarregar ficheiros instantaneamente e com a melhor velocidade da tua Internet.",
"debrid_invalid_token": "Token de API inválido", "real_debrid_invalid_token": "Token de API inválido",
"real_debrid_free_account_error": "A conta \"{{username}}\" é uma conta gratuita. Por favor, subscreve o Real-Debrid", "real_debrid_free_account_error": "A conta \"{{username}}\" é uma conta gratuita. Por favor, subscreve o Real-Debrid",
"debrid_linked_message": "Conta \"{{username}}\" associada", "real_debrid_linked_message": "Conta \"{{username}}\" associada",
"save_changes": "Guardar alterações", "save_changes": "Guardar alterações",
"changes_saved": "Alterações guardadas com sucesso", "changes_saved": "Alterações guardadas com sucesso",
"download_sources_description": "O Hydra vai procurar links de download em todas as fontes ativadas. O URL da fonte deve ser um link direto para um ficheiro .json que contenha uma lista de links.", "download_sources_description": "O Hydra vai procurar links de download em todas as fontes ativadas. O URL da fonte deve ser um link direto para um ficheiro .json que contenha uma lista de links.",

View File

@@ -14,12 +14,10 @@
"paused": "{{title}} (Pauzat)", "paused": "{{title}} (Pauzat)",
"downloading": "{{title}} ({{percentage}} - Se descarcă...)", "downloading": "{{title}} ({{percentage}} - Se descarcă...)",
"filter": "Filtrează biblioteca", "filter": "Filtrează biblioteca",
"home": "Acasă", "home": "Acasă"
"favorites": "Favorite"
}, },
"header": { "header": {
"search": "Caută jocuri", "search": "Caută jocuri",
"home": "Acasă", "home": "Acasă",
"catalogue": "Catalog", "catalogue": "Catalog",
"downloads": "Descărcări", "downloads": "Descărcări",
@@ -126,13 +124,13 @@
"general": "General", "general": "General",
"behavior": "Comportament", "behavior": "Comportament",
"language": "Limbă", "language": "Limbă",
"api_token": "Token API", "real_debrid_api_token": "Token API",
"enable_real_debrid": "Activează Real-Debrid", "enable_real_debrid": "Activează Real-Debrid",
"real_debrid_description": "Real-Debrid este un descărcător fără restricții care îți permite să descarci fișiere instantaneu și la cea mai bună viteză a internetului tău.", "real_debrid_description": "Real-Debrid este un descărcător fără restricții care îți permite să descarci fișiere instantaneu și la cea mai bună viteză a internetului tău.",
"debrid_invalid_token": "Token API invalid", "real_debrid_invalid_token": "Token API invalid",
"debrid_api_token_hint": "Poți obține token-ul tău API <0>aici</0>", "real_debrid_api_token_hint": "Poți obține token-ul tău API <0>aici</0>",
"real_debrid_free_account_error": "Contul \"{{username}}\" este un cont gratuit. Te rugăm să te abonezi la Real-Debrid", "real_debrid_free_account_error": "Contul \"{{username}}\" este un cont gratuit. Te rugăm să te abonezi la Real-Debrid",
"debrid_linked_message": "Contul \"{{username}}\" a fost legat", "real_debrid_linked_message": "Contul \"{{username}}\" a fost legat",
"save_changes": "Salvează modificările", "save_changes": "Salvează modificările",
"changes_saved": "Modificările au fost salvate cu succes" "changes_saved": "Modificările au fost salvate cu succes"
}, },

View File

@@ -7,8 +7,8 @@
"featured": "Рекомендации", "featured": "Рекомендации",
"surprise_me": "Удиви меня", "surprise_me": "Удиви меня",
"no_results": "Ничего не найдено", "no_results": "Ничего не найдено",
"hot": "Сейчас популярно", "hot": "Сейчас в топе",
"start_typing": "Начинаю вводить текст...", "start_typing": "Начинаю вводить текст для поиска...",
"weekly": "📅 Лучшие игры недели", "weekly": "📅 Лучшие игры недели",
"achievements": "🏆 Игры, в которых нужно победить" "achievements": "🏆 Игры, в которых нужно победить"
}, },
@@ -26,8 +26,7 @@
"game_has_no_executable": "Файл запуска игры не выбран", "game_has_no_executable": "Файл запуска игры не выбран",
"sign_in": "Войти", "sign_in": "Войти",
"friends": "Друзья", "friends": "Друзья",
"need_help": "Нужна помощь?", "need_help": "Нужна помощь?"
"favorites": "Избранное"
}, },
"header": { "header": {
"search": "Поиск", "search": "Поиск",
@@ -183,13 +182,7 @@
"no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.", "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.",
"reset_achievements_title": "Вы уверены?", "reset_achievements_title": "Вы уверены?",
"reset_achievements_success": "Достижения успешно сброшены", "reset_achievements_success": "Достижения успешно сброшены",
"reset_achievements_error": "Не удалось сбросить достижения", "reset_achievements_error": "Не удалось сбросить достижения"
"download_error_gofile_quota_exceeded": "Вы превысили месячную квоту Gofile. Пожалуйста, подождите, пока квота не будет восстановлена.",
"download_error_real_debrid_account_not_authorized": "Ваш аккаунт Real-Debrid не авторизован для осуществления новых загрузок. Пожалуйста, проверьте настройки учетной записи и повторите попытку.",
"download_error_not_cached_in_real_debrid": "Эта загрузка недоступна на Real-Debrid, а опрос статуса загрузки с Real-Debrid пока недоступен.",
"download_error_not_cached_in_torbox": "Эта загрузка недоступна на Torbox, и опросить статус загрузки с Torbox пока невозможно.",
"game_added_to_favorites": "Игра добавлена в избранное",
"game_removed_from_favorites": "Игра удалена из избранного"
}, },
"activation": { "activation": {
"title": "Активировать Hydra", "title": "Активировать Hydra",
@@ -244,13 +237,13 @@
"behavior": "Поведение", "behavior": "Поведение",
"download_sources": "Источники загрузки", "download_sources": "Источники загрузки",
"language": "Язык", "language": "Язык",
"api_token": "API Ключ", "real_debrid_api_token": "API Ключ",
"enable_real_debrid": "Включить Real-Debrid", "enable_real_debrid": "Включить Real-Debrid",
"real_debrid_description": "Real-Debrid - это неограниченный загрузчик, который позволяет быстро скачивать файлы, размещенные в Интернете, или мгновенно передавать их в плеер через частную сеть, позволяющую обходить любые блокировки.", "real_debrid_description": "Real-Debrid - это неограниченный загрузчик, который позволяет быстро скачивать файлы, размещенные в Интернете, или мгновенно передавать их в плеер через частную сеть, позволяющую обходить любые блокировки.",
"debrid_invalid_token": "Неверный API ключ", "real_debrid_invalid_token": "Неверный API ключ",
"debrid_api_token_hint": "API ключ можно получить <0>здесь</0>", "real_debrid_api_token_hint": "API ключ можно получить <0>здесь</0>",
"real_debrid_free_account_error": "Аккаунт \"{{username}}\" - не имеет подписки. Пожалуйста, оформите подписку на Real-Debrid", "real_debrid_free_account_error": "Аккаунт \"{{username}}\" - не имеет подписки. Пожалуйста, оформите подписку на Real-Debrid",
"debrid_linked_message": "Привязан аккаунт \"{{username}}\"", "real_debrid_linked_message": "Привязан аккаунт \"{{username}}\"",
"save_changes": "Сохранить изменения", "save_changes": "Сохранить изменения",
"changes_saved": "Изменения успешно сохранены", "changes_saved": "Изменения успешно сохранены",
"download_sources_description": "Hydra будет получать ссылки на загрузки из этих источников. URL должна содержать прямую ссылку на .json-файл с ссылками для загрузок.", "download_sources_description": "Hydra будет получать ссылки на загрузки из этих источников. URL должна содержать прямую ссылку на .json-файл с ссылками для загрузок.",
@@ -301,36 +294,7 @@
"become_subscriber": "Станьте обладателем Hydra Cloud", "become_subscriber": "Станьте обладателем Hydra Cloud",
"subscription_renew_cancelled": "Автоматическое продление отключено", "subscription_renew_cancelled": "Автоматическое продление отключено",
"subscription_renews_on": "Ваша подписка продлевается на {{date}}", "subscription_renews_on": "Ваша подписка продлевается на {{date}}",
"bill_sent_until": "Ваш следующий счет будет отправлен до этого дня", "bill_sent_until": "Ваш следующий счет будет отправлен до этого дня"
"no_themes": "Похоже, что у вас еще нет тем, но не волнуйтесь, нажмите здесь, чтобы создать свой первый шедевр",
"editor_tab_code": "Код",
"editor_tab_info": "Информация",
"editor_tab_save": "Сохранить",
"web_store": "Веб-магазин",
"clear_themes": "Очистить",
"create_theme": "Создать",
"create_theme_modal_title": "Создать пользовательскую тему",
"create_theme_modal_description": "Создать новую тему для настройки внешнего вида Hydra",
"theme_name": "Название",
"insert_theme_name": "Вставить название темы",
"set_theme": "Установить тему",
"unset_theme": "Снять тему",
"delete_theme": "Удалить тему",
"edit_theme": "Редактировать тему",
"delete_all_themes": "Удалить все темы",
"delete_all_themes_description": "Это удалит все ваши пользовательские темы",
"delete_theme_description": "Это приведет к удалению темы {{theme}}",
"cancel": "Отменить",
"appearance": "Внешний вид",
"enable_torbox": "Включить Torbox",
"torbox_description": "TorBox - это ваш премиум-сервис, конкурирующий даже с лучшими серверами на рынке.",
"torbox_account_linked": "Аккаунт TorBox привязан",
"real_debrid_account_linked": "Аккаунт Real-Debrid привязан",
"name_min_length": "Название темы должно содержать не менее 3 символов",
"import_theme": "Импортировать тему",
"import_theme_description": "Вы импортируете {{theme}} из магазина тем",
"error_importing_theme": "Ошибка при импорте темы",
"theme_imported": "Тема успешно импортирована"
}, },
"notifications": { "notifications": {
"download_complete": "Загрузка завершена", "download_complete": "Загрузка завершена",
@@ -460,7 +424,7 @@
"subscribe_now": "Подпишитесь прямо сейчас", "subscribe_now": "Подпишитесь прямо сейчас",
"cloud_saving": "Сохранение в облаке", "cloud_saving": "Сохранение в облаке",
"cloud_achievements": "Сохраняйте свои достижения в облаке", "cloud_achievements": "Сохраняйте свои достижения в облаке",
"animated_profile_picture": "Анимированные аватарки", "animated_profile_picture": "Анимированные фотографии профиля",
"premium_support": "Премиальная поддержка", "premium_support": "Премиальная поддержка",
"show_and_compare_achievements": "Показывайте и сравнивайте свои достижения с достижениями других пользователей", "show_and_compare_achievements": "Показывайте и сравнивайте свои достижения с достижениями других пользователей",
"animated_profile_banner": "Анимированный баннер профиля", "animated_profile_banner": "Анимированный баннер профиля",

View File

@@ -26,8 +26,7 @@
"game_has_no_executable": "Oyun için bir çalıştırılabilir dosya seçilmedi", "game_has_no_executable": "Oyun için bir çalıştırılabilir dosya seçilmedi",
"sign_in": "Giriş yap", "sign_in": "Giriş yap",
"friends": "Arkadaşlar", "friends": "Arkadaşlar",
"need_help": "Yardıma mı ihtiyacınız var?", "need_help": "Yardıma mı ihtiyacınız var?"
"favorites": "Favoriler"
}, },
"header": { "header": {
"search": "Oyunları ara", "search": "Oyunları ara",
@@ -237,13 +236,13 @@
"behavior": "Davranış", "behavior": "Davranış",
"download_sources": "İndirme kaynakları", "download_sources": "İndirme kaynakları",
"language": "Dil", "language": "Dil",
"api_token": "API Anahtarı", "real_debrid_api_token": "API Anahtarı",
"enable_real_debrid": "Real-Debrid'i Etkinleştir", "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_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.",
"debrid_invalid_token": "Geçersiz API anahtarı", "real_debrid_invalid_token": "Geçersiz API anahtarı",
"debrid_api_token_hint": "API anahtarınızı <0>buradan</0> alabilirsiniz", "real_debrid_api_token_hint": "API anahtarınızı <0>buradan</0> alabilirsiniz",
"real_debrid_free_account_error": "\"{{username}}\" hesabı ücretsiz bir hesaptır. Lütfen Real-Debrid abonesi olun", "real_debrid_free_account_error": "\"{{username}}\" hesabı ücretsiz bir hesaptır. Lütfen Real-Debrid abonesi olun",
"debrid_linked_message": "\"{{username}}\" hesabı bağlandı", "real_debrid_linked_message": "\"{{username}}\" hesabı bağlandı",
"save_changes": "Değişiklikleri Kaydet", "save_changes": "Değişiklikleri Kaydet",
"changes_saved": "Değişiklikler başarıyla kaydedildi", "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.", "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.",

View File

@@ -20,12 +20,10 @@
"home": "Головна", "home": "Головна",
"game_has_no_executable": "Не було вибрано файл для запуску гри", "game_has_no_executable": "Не було вибрано файл для запуску гри",
"queued": "{{title}} в черзі", "queued": "{{title}} в черзі",
"sign_in": "Увійти", "sign_in": "Увійти"
"favorites": "Улюблені"
}, },
"header": { "header": {
"search": "Пошук", "search": "Пошук",
"home": "Головна", "home": "Головна",
"catalogue": "Каталог", "catalogue": "Каталог",
"downloads": "Завантаження", "downloads": "Завантаження",
@@ -176,13 +174,13 @@
"import": "Імпортувати", "import": "Імпортувати",
"insert_valid_json_url": "Вставте дійсний URL JSON-файлу", "insert_valid_json_url": "Вставте дійсний URL JSON-файлу",
"language": "Мова", "language": "Мова",
"api_token": "API-токен", "real_debrid_api_token": "API-токен",
"debrid_api_token_hint": "API токен можливо отримати <0>тут</0>", "real_debrid_api_token_hint": "API токен можливо отримати <0>тут</0>",
"real_debrid_api_token_label": "Real-Debrid API-токен", "real_debrid_api_token_label": "Real-Debrid API-токен",
"real_debrid_description": "Real-Debrid — це необмежений завантажувач, який дозволяє швидко завантажувати файли, розміщені в Інтернеті, або миттєво передавати їх у плеєр через приватну мережу, що дозволяє обходити будь-які блокування.", "real_debrid_description": "Real-Debrid — це необмежений завантажувач, який дозволяє швидко завантажувати файли, розміщені в Інтернеті, або миттєво передавати їх у плеєр через приватну мережу, що дозволяє обходити будь-які блокування.",
"real_debrid_free_account_error": "Акаунт \"{{username}}\" - не має наявної підписки. Будь ласка, оформіть підписку на Real-Debrid", "real_debrid_free_account_error": "Акаунт \"{{username}}\" - не має наявної підписки. Будь ласка, оформіть підписку на Real-Debrid",
"debrid_invalid_token": "Невірний API-токен", "real_debrid_invalid_token": "Невірний API-токен",
"debrid_linked_message": "Акаунт \"{{username}}\" привязаний", "real_debrid_linked_message": "Акаунт \"{{username}}\" привязаний",
"remove_download_source": "Видалити", "remove_download_source": "Видалити",
"removed_download_source": "Джерело завантажень було видалено", "removed_download_source": "Джерело завантажень було видалено",
"save_changes": "Зберегти зміни", "save_changes": "Зберегти зміни",

View File

@@ -25,8 +25,7 @@
"queued": "{{title}} (已加入下载队列)", "queued": "{{title}} (已加入下载队列)",
"game_has_no_executable": "未选择游戏的可执行文件", "game_has_no_executable": "未选择游戏的可执行文件",
"sign_in": "登入", "sign_in": "登入",
"friends": "好友", "friends": "好友"
"favorites": "收藏"
}, },
"header": { "header": {
"search": "搜索游戏", "search": "搜索游戏",
@@ -214,13 +213,13 @@
"behavior": "行为", "behavior": "行为",
"download_sources": "下载源", "download_sources": "下载源",
"language": "语言", "language": "语言",
"api_token": "API 令牌", "real_debrid_api_token": "API 令牌",
"enable_real_debrid": "启用 Real-Debrid", "enable_real_debrid": "启用 Real-Debrid",
"real_debrid_description": "Real-Debrid 是一个无限制的下载器,允许您以最快的互联网速度即时下载文件。", "real_debrid_description": "Real-Debrid 是一个无限制的下载器,允许您以最快的互联网速度即时下载文件。",
"debrid_invalid_token": "无效的 API 令牌", "real_debrid_invalid_token": "无效的 API 令牌",
"debrid_api_token_hint": "您可以从<0>这里</0>获取API密钥.", "real_debrid_api_token_hint": "您可以从<0>这里</0>获取API密钥.",
"real_debrid_free_account_error": "账户 \"{{username}}\" 是免费账户。请订阅 Real-Debrid", "real_debrid_free_account_error": "账户 \"{{username}}\" 是免费账户。请订阅 Real-Debrid",
"debrid_linked_message": "账户 \"{{username}}\" 已链接", "real_debrid_linked_message": "账户 \"{{username}}\" 已链接",
"save_changes": "保存更改", "save_changes": "保存更改",
"changes_saved": "更改已成功保存", "changes_saved": "更改已成功保存",
"download_sources_description": "Hydra 将从这些源获取下载链接。源 URL 必须是直接链接到包含下载链接的 .json 文件。", "download_sources_description": "Hydra 将从这些源获取下载链接。源 URL 必须是直接链接到包含下载链接的 .json 文件。",

View File

@@ -7,18 +7,13 @@ export const defaultDownloadsPath = app.getPath("downloads");
export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging"); export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging");
export const levelDatabasePath = path.join(
app.getPath("userData"),
`hydra-db${isStaging ? "-staging" : ""}`
);
export const databaseDirectory = path.join(app.getPath("appData"), "hydra"); export const databaseDirectory = path.join(app.getPath("appData"), "hydra");
export const databasePath = path.join( export const databasePath = path.join(
databaseDirectory, databaseDirectory,
isStaging ? "hydra_test.db" : "hydra.db" isStaging ? "hydra_test.db" : "hydra.db"
); );
export const logsPath = path.join(app.getPath("userData"), "logs"); export const logsPath = path.join(app.getPath("appData"), "hydra", "logs");
export const seedsPath = app.isPackaged export const seedsPath = app.isPackaged
? path.join(process.resourcesPath, "seeds") ? path.join(process.resourcesPath, "seeds")

27
src/main/data-source.ts Normal file
View File

@@ -0,0 +1,27 @@
import { DataSource } from "typeorm";
import {
DownloadQueue,
Game,
GameShopCache,
UserPreferences,
UserAuth,
GameAchievement,
UserSubscription,
} from "@main/entity";
import { databasePath } from "./constants";
export const dataSource = new DataSource({
type: "better-sqlite3",
entities: [
Game,
UserAuth,
UserPreferences,
UserSubscription,
GameShopCache,
DownloadQueue,
GameAchievement,
],
synchronize: false,
database: databasePath,
});

View File

@@ -0,0 +1,25 @@
import {
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
OneToOne,
JoinColumn,
} from "typeorm";
import type { Game } from "./game.entity";
@Entity("download_queue")
export class DownloadQueue {
@PrimaryGeneratedColumn()
id: number;
@OneToOne("Game", "downloadQueue")
@JoinColumn()
game: Game;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@@ -0,0 +1,19 @@
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity("game_achievement")
export class GameAchievement {
@PrimaryGeneratedColumn()
id: number;
@Column("text")
objectId: string;
@Column("text")
shop: string;
@Column("text", { nullable: true })
unlockedAchievements: string | null;
@Column("text", { nullable: true })
achievements: string | null;
}

View File

@@ -0,0 +1,35 @@
import {
Entity,
PrimaryColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
import type { GameShop } from "@types";
@Entity("game_shop_cache")
export class GameShopCache {
@PrimaryColumn("text", { unique: true })
objectID: string;
@Column("text")
shop: GameShop;
@Column("text", { nullable: true })
serializedData: string;
/**
* @deprecated Use IndexedDB's `howLongToBeatEntries` instead
*/
@Column("text", { nullable: true })
howLongToBeatSerializedData: string;
@Column("text", { nullable: true })
language: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@@ -0,0 +1,90 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToOne,
} from "typeorm";
import type { GameShop, GameStatus } from "@types";
import { Downloader } from "@shared";
import type { DownloadQueue } from "./download-queue.entity";
@Entity("game")
export class Game {
@PrimaryGeneratedColumn()
id: number;
@Column("text", { unique: true })
objectID: string;
@Column("text", { unique: true, nullable: true })
remoteId: string | null;
@Column("text")
title: string;
@Column("text", { nullable: true })
iconUrl: string | null;
@Column("text", { nullable: true })
folderName: string | null;
@Column("text", { nullable: true })
downloadPath: string | null;
@Column("text", { nullable: true })
executablePath: string | null;
@Column("text", { nullable: true })
launchOptions: string | null;
@Column("text", { nullable: true })
winePrefixPath: string | null;
@Column("int", { default: 0 })
playTimeInMilliseconds: number;
@Column("text")
shop: GameShop;
@Column("text", { nullable: true })
status: GameStatus | null;
@Column("int", { default: Downloader.Torrent })
downloader: Downloader;
/**
* Progress is a float between 0 and 1
*/
@Column("float", { default: 0 })
progress: number;
@Column("int", { default: 0 })
bytesDownloaded: number;
@Column("datetime", { nullable: true })
lastTimePlayed: Date | null;
@Column("float", { default: 0 })
fileSize: number;
@Column("text", { nullable: true })
uri: string | null;
@OneToOne("DownloadQueue", "game")
downloadQueue: DownloadQueue;
@Column("boolean", { default: false })
isDeleted: boolean;
@Column("boolean", { default: false })
shouldSeed: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

8
src/main/entity/index.ts Normal file
View File

@@ -0,0 +1,8 @@
export * from "./game.entity";
export * from "./user-auth.entity";
export * from "./user-preferences.entity";
export * from "./user-subscription.entity";
export * from "./game-shop-cache.entity";
export * from "./game.entity";
export * from "./game-achievements.entity";
export * from "./download-queue.entity";

View File

@@ -0,0 +1,45 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToOne,
} from "typeorm";
import { UserSubscription } from "./user-subscription.entity";
@Entity("user_auth")
export class UserAuth {
@PrimaryGeneratedColumn()
id: number;
@Column("text", { default: "" })
userId: string;
@Column("text", { default: "" })
displayName: string;
@Column("text", { nullable: true })
profileImageUrl: string | null;
@Column("text", { nullable: true })
backgroundImageUrl: string | null;
@Column("text", { default: "" })
accessToken: string;
@Column("text", { default: "" })
refreshToken: string;
@Column("int", { default: 0 })
tokenExpirationTimestamp: number;
@OneToOne("UserSubscription", "user")
subscription: UserSubscription | null;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@@ -0,0 +1,55 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
@Entity("user_preferences")
export class UserPreferences {
@PrimaryGeneratedColumn()
id: number;
@Column("text", { nullable: true })
downloadsPath: string | null;
@Column("text", { default: "en" })
language: string;
@Column("text", { nullable: true })
realDebridApiToken: string | null;
@Column("boolean", { default: false })
downloadNotificationsEnabled: boolean;
@Column("boolean", { default: false })
repackUpdatesNotificationsEnabled: boolean;
@Column("boolean", { default: true })
achievementNotificationsEnabled: boolean;
@Column("boolean", { default: false })
preferQuitInsteadOfHiding: boolean;
@Column("boolean", { default: false })
runAtStartup: boolean;
@Column("boolean", { default: false })
startMinimized: boolean;
@Column("boolean", { default: false })
disableNsfwAlert: boolean;
@Column("boolean", { default: true })
seedAfterDownloadComplete: boolean;
@Column("boolean", { default: false })
showHiddenAchievementsDescription: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@@ -0,0 +1,42 @@
import type { SubscriptionStatus } from "@types";
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToOne,
JoinColumn,
} from "typeorm";
import { UserAuth } from "./user-auth.entity";
@Entity("user_subscription")
export class UserSubscription {
@PrimaryGeneratedColumn()
id: number;
@Column("text", { default: "" })
subscriptionId: string;
@OneToOne("UserAuth", "subscription")
@JoinColumn()
user: UserAuth;
@Column("text", { default: "" })
status: SubscriptionStatus;
@Column("text", { default: "" })
planId: string;
@Column("text", { default: "" })
planName: string;
@Column("datetime", { nullable: true })
expiresAt: Date | null;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@@ -1,11 +1,7 @@
import { WindowManager } from "@main/services";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { WindowManager } from "@main/services";
const openEditorWindow = async ( const openEditorWindow = async (_event: Electron.IpcMainInvokeEvent) =>
_event: Electron.IpcMainInvokeEvent, WindowManager.openEditorWindow();
themeId: string
) => {
WindowManager.openEditorWindow(themeId);
};
registerEvent("openEditorWindow", openEditorWindow); registerEvent("openEditorWindow", openEditorWindow);

View File

@@ -1,13 +1,10 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { userAuthRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { db, levelKeys } from "@main/level";
import type { Auth } from "@types";
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
const auth = await db.get<string, Auth>(levelKeys.auth, { const auth = await userAuthRepository.findOne({ where: { id: 1 } });
valueEncoding: "json",
});
if (!auth) return null; if (!auth) return null;
const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload; const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload;

View File

@@ -1,29 +1,35 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services"; import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services";
import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; import { dataSource } from "@main/data-source";
import { DownloadQueue, Game, UserAuth, UserSubscription } from "@main/entity";
import { PythonRPC } from "@main/services/python-rpc";
const signOut = async (_event: Electron.IpcMainInvokeEvent) => { const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
const databaseOperations = db const databaseOperations = dataSource
.batch([ .transaction(async (transactionalEntityManager) => {
{ await transactionalEntityManager.getRepository(DownloadQueue).delete({});
type: "del",
key: levelKeys.auth, await transactionalEntityManager.getRepository(Game).delete({});
},
{ await transactionalEntityManager
type: "del", .getRepository(UserAuth)
key: levelKeys.user, .delete({ id: 1 });
},
]) await transactionalEntityManager
.getRepository(UserSubscription)
.delete({ id: 1 });
})
.then(() => { .then(() => {
/* Removes all games being played */ /* Removes all games being played */
gamesPlaytime.clear(); gamesPlaytime.clear();
return Promise.all([gamesSublevel.clear(), downloadsSublevel.clear()]);
}); });
/* Cancels any ongoing downloads */ /* Cancels any ongoing downloads */
DownloadManager.cancelDownload(); DownloadManager.cancelDownload();
/* Disconnects libtorrent */
PythonRPC.kill();
HydraApi.handleSignOut(); HydraApi.handleSignOut();
await Promise.all([ await Promise.all([

View File

@@ -1,8 +1,47 @@
import type { AppUpdaterEvent } from "@types";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { UpdateManager } from "@main/services/update-manager"; import updater, { UpdateInfo } from "electron-updater";
import { WindowManager } from "@main/services";
import { app } from "electron";
import { publishNotificationUpdateReadyToInstall } from "@main/services/notifications";
const { autoUpdater } = updater;
const sendEvent = (event: AppUpdaterEvent) => {
WindowManager.mainWindow?.webContents.send("autoUpdaterEvent", event);
};
const sendEventsForDebug = false;
const isAutoInstallAvailable =
process.platform !== "darwin" && process.env.PORTABLE_EXECUTABLE_FILE == null;
const mockValuesForDebug = () => {
sendEvent({ type: "update-available", info: { version: "1.3.0" } });
sendEvent({ type: "update-downloaded" });
};
const newVersionInfo = { version: "" };
const checkForUpdates = async (_event: Electron.IpcMainInvokeEvent) => { const checkForUpdates = async (_event: Electron.IpcMainInvokeEvent) => {
return UpdateManager.checkForUpdates(); autoUpdater
.once("update-available", (info: UpdateInfo) => {
sendEvent({ type: "update-available", info });
newVersionInfo.version = info.version;
})
.once("update-downloaded", () => {
sendEvent({ type: "update-downloaded" });
publishNotificationUpdateReadyToInstall(newVersionInfo.version);
});
if (app.isPackaged) {
autoUpdater.autoDownload = isAutoInstallAvailable;
autoUpdater.checkForUpdates();
} else if (sendEventsForDebug) {
mockValuesForDebug();
}
return isAutoInstallAvailable;
}; };
registerEvent("checkForUpdates", checkForUpdates); registerEvent("checkForUpdates", checkForUpdates);

View File

@@ -1,10 +1,10 @@
import { getSteamAppDetails, logger } from "@main/services"; import { gameShopCacheRepository } from "@main/repository";
import { getSteamAppDetails } from "@main/services";
import type { ShopDetails, GameShop } from "@types"; import type { ShopDetails, GameShop, SteamAppDetails } from "@types";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { steamGamesWorker } from "@main/workers"; import { steamGamesWorker } from "@main/workers";
import { gamesShopCacheSublevel, levelKeys } from "@main/level";
const getLocalizedSteamAppDetails = async ( const getLocalizedSteamAppDetails = async (
objectId: string, objectId: string,
@@ -39,27 +39,35 @@ const getGameShopDetails = async (
language: string language: string
): Promise<ShopDetails | null> => { ): Promise<ShopDetails | null> => {
if (shop === "steam") { if (shop === "steam") {
const cachedData = await gamesShopCacheSublevel.get( const cachedData = await gameShopCacheRepository.findOne({
levelKeys.gameShopCacheItem(shop, objectId, language) where: { objectID: objectId, language },
); });
const appDetails = getLocalizedSteamAppDetails(objectId, language).then( const appDetails = getLocalizedSteamAppDetails(objectId, language).then(
(result) => { (result) => {
if (result) { if (result) {
gamesShopCacheSublevel gameShopCacheRepository.upsert(
.put(levelKeys.gameShopCacheItem(shop, objectId, language), result) {
.catch((err) => { objectID: objectId,
logger.error("Could not cache game details", err); shop: "steam",
}); language,
serializedData: JSON.stringify(result),
},
["objectID"]
);
} }
return result; return result;
} }
); );
if (cachedData) { const cachedGame = cachedData?.serializedData
? (JSON.parse(cachedData?.serializedData) as SteamAppDetails)
: null;
if (cachedGame) {
return { return {
...cachedData, ...cachedGame,
objectId, objectId,
} as ShopDetails; } as ShopDetails;
} }

View File

@@ -1,14 +1,14 @@
import { db, levelKeys } from "@main/level";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services"; import { HydraApi } from "@main/services";
import { userPreferencesRepository } from "@main/repository";
import type { TrendingGame } from "@types"; import type { TrendingGame } from "@types";
const getTrendingGames = async (_event: Electron.IpcMainInvokeEvent) => { const getTrendingGames = async (_event: Electron.IpcMainInvokeEvent) => {
const language = await db const userPreferences = await userPreferencesRepository.findOne({
.get<string, string>(levelKeys.language, { where: { id: 1 },
valueEncoding: "utf-8", });
})
.then((language) => language || "en"); const language = userPreferences?.language || "en";
const trendingGames = await HydraApi.get<TrendingGame[]>( const trendingGames = await HydraApi.get<TrendingGame[]>(
"/games/trending", "/games/trending",

View File

@@ -1,14 +1,19 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import type { GameShop } from "@types"; import type { GameShop } from "@types";
import { Ludusavi } from "@main/services"; import { Ludusavi } from "@main/services";
import { gamesSublevel, levelKeys } from "@main/level"; import { gameRepository } from "@main/repository";
const getGameBackupPreview = async ( const getGameBackupPreview = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
objectId: string, objectId: string,
shop: GameShop shop: GameShop
) => { ) => {
const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); const game = await gameRepository.findOne({
where: {
objectID: objectId,
shop,
},
});
return Ludusavi.getBackupPreview(shop, objectId, game?.winePrefixPath); return Ludusavi.getBackupPreview(shop, objectId, game?.winePrefixPath);
}; };

View File

@@ -10,7 +10,7 @@ import os from "node:os";
import { backupsPath } from "@main/constants"; import { backupsPath } from "@main/constants";
import { app } from "electron"; import { app } from "electron";
import { normalizePath } from "@main/helpers"; import { normalizePath } from "@main/helpers";
import { gamesSublevel, levelKeys } from "@main/level"; import { gameRepository } from "@main/repository";
const bundleBackup = async ( const bundleBackup = async (
shop: GameShop, shop: GameShop,
@@ -46,7 +46,12 @@ const uploadSaveGame = async (
shop: GameShop, shop: GameShop,
downloadOptionTitle: string | null downloadOptionTitle: string | null
) => { ) => {
const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); const game = await gameRepository.findOne({
where: {
objectID: objectId,
shop,
},
});
const bundleLocation = await bundleBackup( const bundleLocation = await bundleBackup(
shop, shop,

View File

@@ -1,21 +1,15 @@
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
const checkFolderWritePermission = async ( const checkFolderWritePermission = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
testPath: string path: string
) => { ) =>
const testFilePath = path.join(testPath, ".hydra-write-test"); new Promise((resolve) => {
fs.access(path, fs.constants.W_OK, (err) => {
try { resolve(!err);
fs.writeFileSync(testFilePath, ""); });
fs.rmSync(testFilePath); });
return true;
} catch (err) {
return false;
}
};
registerEvent("checkFolderWritePermission", checkFolderWritePermission); registerEvent("checkFolderWritePermission", checkFolderWritePermission);

View File

@@ -0,0 +1,44 @@
import { Document as YMLDocument } from "yaml";
import { Game } from "@main/entity";
import path from "node:path";
export const generateYML = (game: Game) => {
const slugifiedGameTitle = game.title.replace(/\s/g, "-").toLocaleLowerCase();
const doc = new YMLDocument({
name: game.title,
game_slug: slugifiedGameTitle,
slug: `${slugifiedGameTitle}-installer`,
version: "Installer",
runner: "wine",
script: {
game: {
prefix: "$GAMEDIR",
arch: "win64",
working_dir: "$GAMEDIR",
},
installer: [
{
task: {
name: "create_prefix",
arch: "win64",
prefix: "$GAMEDIR",
},
},
{
task: {
executable: path.join(
game.downloadPath!,
game.folderName!,
"setup.exe"
),
name: "wineexec",
prefix: "$GAMEDIR",
},
},
],
},
});
return doc.toString();
};

View File

@@ -1,16 +1,15 @@
import { userPreferencesRepository } from "@main/repository";
import { defaultDownloadsPath } from "@main/constants"; import { defaultDownloadsPath } from "@main/constants";
import { db, levelKeys } from "@main/level";
import type { UserPreferences } from "@types";
export const getDownloadsPath = async () => { export const getDownloadsPath = async () => {
const userPreferences = await db.get<string, UserPreferences | null>( const userPreferences = await userPreferencesRepository.findOne({
levelKeys.userPreferences, where: {
{ id: 1,
valueEncoding: "json", },
} });
);
if (userPreferences?.downloadsPath) return userPreferences.downloadsPath; if (userPreferences && userPreferences.downloadsPath)
return userPreferences.downloadsPath;
return defaultDownloadsPath; return defaultDownloadsPath;
}; };

View File

@@ -1,7 +0,0 @@
export const parseLaunchOptions = (params?: string | null): string[] => {
if (!params) {
return [];
}
return params.split(" ");
};

View File

@@ -13,8 +13,6 @@ import "./catalogue/get-developers";
import "./hardware/get-disk-free-space"; import "./hardware/get-disk-free-space";
import "./hardware/check-folder-write-permission"; import "./hardware/check-folder-write-permission";
import "./library/add-game-to-library"; import "./library/add-game-to-library";
import "./library/add-game-to-favorites";
import "./library/remove-game-from-favorites";
import "./library/create-game-shortcut"; import "./library/create-game-shortcut";
import "./library/close-game"; import "./library/close-game";
import "./library/delete-game-folder"; import "./library/delete-game-folder";
@@ -48,10 +46,10 @@ import "./user-preferences/auto-launch";
import "./autoupdater/check-for-updates"; import "./autoupdater/check-for-updates";
import "./autoupdater/restart-and-install-update"; import "./autoupdater/restart-and-install-update";
import "./user-preferences/authenticate-real-debrid"; import "./user-preferences/authenticate-real-debrid";
import "./user-preferences/authenticate-torbox";
import "./download-sources/put-download-source"; import "./download-sources/put-download-source";
import "./auth/sign-out"; import "./auth/sign-out";
import "./auth/open-auth-window"; import "./auth/open-auth-window";
import "./aparence/open-editor-window";
import "./auth/get-session-hash"; import "./auth/get-session-hash";
import "./user/get-user"; import "./user/get-user";
import "./user/get-blocked-users"; import "./user/get-blocked-users";
@@ -77,16 +75,6 @@ import "./cloud-save/upload-save-game";
import "./cloud-save/delete-game-artifact"; import "./cloud-save/delete-game-artifact";
import "./cloud-save/select-game-backup-path"; import "./cloud-save/select-game-backup-path";
import "./notifications/publish-new-repacks-notification"; import "./notifications/publish-new-repacks-notification";
import "./themes/add-custom-theme";
import "./themes/delete-custom-theme";
import "./themes/get-all-custom-themes";
import "./themes/delete-all-custom-themes";
import "./themes/update-custom-theme";
import "./themes/open-editor-window";
import "./themes/get-custom-theme-by-id";
import "./themes/get-active-custom-theme";
import "./themes/close-editor-window";
import "./themes/toggle-custom-theme";
import { isPortableVersion } from "@main/helpers"; import { isPortableVersion } from "@main/helpers";
ipcMain.handle("ping", () => "pong"); ipcMain.handle("ping", () => "pong");

View File

@@ -1,25 +0,0 @@
import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import type { GameShop } from "@types";
const addGameToFavorites = async (
_event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string
) => {
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (!game) return;
try {
await gamesSublevel.put(gameKey, {
...game,
favorite: true,
});
} catch (error) {
throw new Error(`Failed to update game favorite status: ${error}`);
}
};
registerEvent("addGameToFavorites", addGameToFavorites);

View File

@@ -1,55 +1,57 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import type { Game, GameShop } from "@types"; import type { GameShop } from "@types";
import { steamGamesWorker } from "@main/workers"; import { steamGamesWorker } from "@main/workers";
import { createGame } from "@main/services/library-sync"; import { createGame } from "@main/services/library-sync";
import { steamUrlBuilder } from "@shared"; import { steamUrlBuilder } from "@shared";
import { updateLocalUnlockedAchievements } from "@main/services/achievements/update-local-unlocked-achivements"; import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements";
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
const addGameToLibrary = async ( const addGameToLibrary = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string, objectId: string,
title: string title: string,
shop: GameShop
) => { ) => {
const gameKey = levelKeys.game(shop, objectId); return gameRepository
const game = await gamesSublevel.get(gameKey); .update(
{
objectID: objectId,
},
{
shop,
status: null,
isDeleted: false,
}
)
.then(async ({ affected }) => {
if (!affected) {
const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById",
});
if (game) { const iconUrl = steamGame?.clientIcon
await downloadsSublevel.del(gameKey); ? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null;
await gamesSublevel.put(gameKey, { await gameRepository.insert({
...game, title,
isDeleted: false, iconUrl,
objectID: objectId,
shop,
});
}
const game = await gameRepository.findOne({
where: { objectID: objectId },
});
updateLocalUnlockedAchivements(game!);
createGame(game!).catch(() => {});
}); });
} else {
const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById",
});
const iconUrl = steamGame?.clientIcon
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null;
const game: Game = {
title,
iconUrl,
objectId,
shop,
remoteId: null,
isDeleted: false,
playTimeInMilliseconds: 0,
lastTimePlayed: null,
};
await gamesSublevel.put(levelKeys.game(shop, objectId), game);
await createGame(game).catch(() => {});
updateLocalUnlockedAchievements(game);
}
}; };
registerEvent("addGameToLibrary", addGameToLibrary); registerEvent("addGameToLibrary", addGameToLibrary);

View File

@@ -1,11 +1,10 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { logger } from "@main/services"; import { logger } from "@main/services";
import sudo from "sudo-prompt"; import sudo from "sudo-prompt";
import { app } from "electron"; import { app } from "electron";
import { PythonRPC } from "@main/services/python-rpc"; import { PythonRPC } from "@main/services/python-rpc";
import { ProcessPayload } from "@main/services/download/types"; import { ProcessPayload } from "@main/services/download/types";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const getKillCommand = (pid: number) => { const getKillCommand = (pid: number) => {
if (process.platform == "win32") { if (process.platform == "win32") {
@@ -17,14 +16,15 @@ const getKillCommand = (pid: number) => {
const closeGame = async ( const closeGame = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const processes = const processes =
(await PythonRPC.rpc.get<ProcessPayload[] | null>("/process-list")).data || (await PythonRPC.rpc.get<ProcessPayload[] | null>("/process-list")).data ||
[]; [];
const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); const game = await gameRepository.findOne({
where: { id: gameId, isDeleted: false },
});
if (!game) return; if (!game) return;

View File

@@ -1,18 +1,18 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { IsNull, Not } from "typeorm";
import createDesktopShortcut from "create-desktop-shortcuts"; import createDesktopShortcut from "create-desktop-shortcuts";
import path from "node:path"; import path from "node:path";
import { app } from "electron"; import { app } from "electron";
import { removeSymbolsFromName } from "@shared"; import { removeSymbolsFromName } from "@shared";
import { GameShop } from "@types";
import { gamesSublevel, levelKeys } from "@main/level";
const createGameShortcut = async ( const createGameShortcut = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, id: number
objectId: string
): Promise<boolean> => { ): Promise<boolean> => {
const gameKey = levelKeys.game(shop, objectId); const game = await gameRepository.findOne({
const game = await gamesSublevel.get(gameKey); where: { id, executablePath: Not(IsNull()) },
});
if (game) { if (game) {
const filePath = game.executablePath; const filePath = game.executablePath;

View File

@@ -1,27 +1,37 @@
import path from "node:path"; import path from "node:path";
import fs from "node:fs"; import fs from "node:fs";
import { gameRepository } from "@main/repository";
import { getDownloadsPath } from "../helpers/get-downloads-path"; import { getDownloadsPath } from "../helpers/get-downloads-path";
import { logger } from "@main/services"; import { logger } from "@main/services";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { GameShop } from "@types";
import { downloadsSublevel, levelKeys } from "@main/level";
const deleteGameFolder = async ( const deleteGameFolder = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
): Promise<void> => { ): Promise<void> => {
const downloadKey = levelKeys.game(shop, objectId); const game = await gameRepository.findOne({
where: [
{
id: gameId,
isDeleted: false,
status: "removed",
},
{
id: gameId,
progress: 1,
isDeleted: false,
},
],
});
const download = await downloadsSublevel.get(downloadKey); if (!game) return;
if (!download) return; if (game.folderName) {
if (download.folderName) {
const folderPath = path.join( const folderPath = path.join(
download.downloadPath ?? (await getDownloadsPath()), game.downloadPath ?? (await getDownloadsPath()),
download.folderName game.folderName
); );
if (fs.existsSync(folderPath)) { if (fs.existsSync(folderPath)) {
@@ -42,7 +52,10 @@ const deleteGameFolder = async (
} }
} }
await downloadsSublevel.del(downloadKey); await gameRepository.update(
{ id: gameId },
{ downloadPath: null, folderName: null, status: null, progress: 0 }
);
}; };
registerEvent("deleteGameFolder", deleteGameFolder); registerEvent("deleteGameFolder", deleteGameFolder);

View File

@@ -1,21 +1,16 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel, downloadsSublevel, levelKeys } from "@main/level";
import type { GameShop } from "@types";
const getGameByObjectId = async ( const getGameByObjectId = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string objectId: string
) => { ) =>
const gameKey = levelKeys.game(shop, objectId); gameRepository.findOne({
const [game, download] = await Promise.all([ where: {
gamesSublevel.get(gameKey), objectID: objectId,
downloadsSublevel.get(gameKey), isDeleted: false,
]); },
});
if (!game || game.isDeleted) return null;
return { id: gameKey, ...game, download };
};
registerEvent("getGameByObjectId", getGameByObjectId); registerEvent("getGameByObjectId", getGameByObjectId);

View File

@@ -1,26 +1,17 @@
import type { LibraryGame } from "@types"; import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { downloadsSublevel, gamesSublevel } from "@main/level";
const getLibrary = async (): Promise<LibraryGame[]> => { const getLibrary = async () =>
return gamesSublevel gameRepository.find({
.iterator() where: {
.all() isDeleted: false,
.then((results) => { },
return Promise.all( relations: {
results downloadQueue: true,
.filter(([_key, game]) => game.isDeleted === false) },
.map(async ([key, game]) => { order: {
const download = await downloadsSublevel.get(key); createdAt: "desc",
},
return { });
id: key,
...game,
download: download ?? null,
};
})
);
});
};
registerEvent("getLibrary", getLibrary); registerEvent("getLibrary", getLibrary);

View File

@@ -1,14 +1,14 @@
import { shell } from "electron"; import { shell } from "electron";
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const openGameExecutablePath = async ( const openGameExecutablePath = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); const game = await gameRepository.findOne({
where: { id: gameId, isDeleted: false },
});
if (!game || !game.executablePath) return; if (!game || !game.executablePath) return;

View File

@@ -1,22 +1,22 @@
import { shell } from "electron"; import { shell } from "electron";
import path from "node:path"; import path from "node:path";
import { gameRepository } from "@main/repository";
import { getDownloadsPath } from "../helpers/get-downloads-path"; import { getDownloadsPath } from "../helpers/get-downloads-path";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { GameShop } from "@types";
import { downloadsSublevel, levelKeys } from "@main/level";
const openGameInstallerPath = async ( const openGameInstallerPath = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const download = await downloadsSublevel.get(levelKeys.game(shop, objectId)); const game = await gameRepository.findOne({
where: { id: gameId, isDeleted: false },
});
if (!download || !download.folderName || !download.downloadPath) return true; if (!game || !game.folderName || !game.downloadPath) return true;
const gamePath = path.join( const gamePath = path.join(
download.downloadPath ?? (await getDownloadsPath()), game.downloadPath ?? (await getDownloadsPath()),
download.folderName! game.folderName!
); );
shell.showItemInFolder(gamePath); shell.showItemInFolder(gamePath);

View File

@@ -1,12 +1,14 @@
import { shell } from "electron"; import { shell } from "electron";
import path from "node:path"; import path from "node:path";
import fs from "node:fs"; import fs from "node:fs";
import { writeFile } from "node:fs/promises";
import { spawnSync, exec } from "node:child_process"; import { spawnSync, exec } from "node:child_process";
import { gameRepository } from "@main/repository";
import { generateYML } from "../helpers/generate-lutris-yaml";
import { getDownloadsPath } from "../helpers/get-downloads-path"; import { getDownloadsPath } from "../helpers/get-downloads-path";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { downloadsSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const executeGameInstaller = (filePath: string) => { const executeGameInstaller = (filePath: string) => {
if (process.platform === "win32") { if (process.platform === "win32") {
@@ -24,21 +26,21 @@ const executeGameInstaller = (filePath: string) => {
const openGameInstaller = async ( const openGameInstaller = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const downloadKey = levelKeys.game(shop, objectId); const game = await gameRepository.findOne({
const download = await downloadsSublevel.get(downloadKey); where: { id: gameId, isDeleted: false },
});
if (!download?.folderName) return true; if (!game || !game.folderName) return true;
const gamePath = path.join( const gamePath = path.join(
download.downloadPath ?? (await getDownloadsPath()), game.downloadPath ?? (await getDownloadsPath()),
download.folderName game.folderName!
); );
if (!fs.existsSync(gamePath)) { if (!fs.existsSync(gamePath)) {
await downloadsSublevel.del(downloadKey); await gameRepository.update({ id: gameId }, { status: null });
return true; return true;
} }
@@ -68,6 +70,13 @@ const openGameInstaller = async (
); );
} }
if (spawnSync("which", ["lutris"]).status === 0) {
const ymlPath = path.join(gamePath, "setup.yml");
await writeFile(ymlPath, generateYML(game));
exec(`lutris --install "${ymlPath}"`);
return true;
}
shell.openPath(gamePath); shell.openPath(gamePath);
return true; return true;
}; };

View File

@@ -1,39 +1,24 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { shell } from "electron"; import { shell } from "electron";
import { spawn } from "child_process";
import { parseExecutablePath } from "../helpers/parse-executable-path"; import { parseExecutablePath } from "../helpers/parse-executable-path";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
import { parseLaunchOptions } from "../helpers/parse-launch-options";
const openGame = async ( const openGame = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number,
objectId: string,
executablePath: string, executablePath: string,
launchOptions?: string | null launchOptions: string | null
) => { ) => {
// TODO: revisit this for launchOptions
const parsedPath = parseExecutablePath(executablePath); const parsedPath = parseExecutablePath(executablePath);
const parsedParams = parseLaunchOptions(launchOptions);
const gameKey = levelKeys.game(shop, objectId); await gameRepository.update(
{ id: gameId },
{ executablePath: parsedPath, launchOptions }
);
const game = await gamesSublevel.get(gameKey); shell.openPath(parsedPath);
if (!game) return;
await gamesSublevel.put(gameKey, {
...game,
executablePath: parsedPath,
launchOptions,
});
if (parsedParams.length === 0) {
shell.openPath(parsedPath);
return;
}
spawn(parsedPath, parsedParams, { shell: false, detached: true });
}; };
registerEvent("openGame", openGame); registerEvent("openGame", openGame);

View File

@@ -1,25 +0,0 @@
import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import type { GameShop } from "@types";
const removeGameFromFavorites = async (
_event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string
) => {
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (!game) return;
try {
await gamesSublevel.put(gameKey, {
...game,
favorite: false,
});
} catch (error) {
throw new Error(`Failed to update game favorite status: ${error}`);
}
};
registerEvent("removeGameFromFavorites", removeGameFromFavorites);

View File

@@ -1,26 +1,26 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services"; import { gameRepository } from "../../repository";
import { gamesSublevel, levelKeys } from "@main/level"; import { HydraApi, logger } from "@main/services";
import type { GameShop } from "@types";
const removeGameFromLibrary = async ( const removeGameFromLibrary = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const gameKey = levelKeys.game(shop, objectId); gameRepository.update(
const game = await gamesSublevel.get(gameKey); { id: gameId },
{ isDeleted: true, executablePath: null }
);
if (game) { removeRemoveGameFromLibrary(gameId).catch((err) => {
await gamesSublevel.put(gameKey, { logger.error("removeRemoveGameFromLibrary", err);
...game, });
isDeleted: true, };
executablePath: null,
});
if (game?.remoteId) { const removeRemoveGameFromLibrary = async (gameId: number) => {
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {}); const game = await gameRepository.findOne({ where: { id: gameId } });
}
if (game?.remoteId) {
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
} }
}; };

View File

@@ -1,14 +1,21 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { levelKeys, downloadsSublevel } from "@main/level"; import { gameRepository } from "../../repository";
import { GameShop } from "@types";
const removeGame = async ( const removeGame = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const downloadKey = levelKeys.game(shop, objectId); await gameRepository.update(
await downloadsSublevel.del(downloadKey); {
id: gameId,
},
{
status: "removed",
downloadPath: null,
bytesDownloaded: 0,
progress: 0,
}
);
}; };
registerEvent("removeGame", removeGame); registerEvent("removeGame", removeGame);

View File

@@ -1,22 +1,16 @@
import { gameAchievementRepository, gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { findAchievementFiles } from "@main/services/achievements/find-achivement-files"; import { findAchievementFiles } from "@main/services/achievements/find-achivement-files";
import fs from "fs"; import fs from "fs";
import { achievementsLogger, HydraApi, WindowManager } from "@main/services"; import { achievementsLogger, HydraApi, WindowManager } from "@main/services";
import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; import { getUnlockedAchievements } from "../user/get-unlocked-achievements";
import {
gameAchievementsSublevel,
gamesSublevel,
levelKeys,
} from "@main/level";
import type { GameShop } from "@types";
const resetGameAchievements = async ( const resetGameAchievements = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
try { try {
const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); const game = await gameRepository.findOne({ where: { id: gameId } });
if (!game) return; if (!game) return;
@@ -29,34 +23,28 @@ const resetGameAchievements = async (
} }
} }
const levelKey = levelKeys.game(game.shop, game.objectId); await gameAchievementRepository.update(
{ objectId: game.objectID },
await gameAchievementsSublevel {
.get(levelKey) unlockedAchievements: null,
.then(async (gameAchievements) => { }
if (gameAchievements) { );
await gameAchievementsSublevel.put(levelKey, {
...gameAchievements,
unlockedAchievements: [],
});
}
});
await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then( await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then(
() => () =>
achievementsLogger.log( achievementsLogger.log(
`Deleted achievements from ${game.remoteId} - ${game.objectId} - ${game.title}` `Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}`
) )
); );
const gameAchievements = await getUnlockedAchievements( const gameAchievements = await getUnlockedAchievements(
game.objectId, game.objectID,
game.shop, game.shop,
true true
); );
WindowManager.mainWindow?.webContents.send( WindowManager.mainWindow?.webContents.send(
`on-update-achievements-${game.objectId}-${game.shop}`, `on-update-achievements-${game.objectID}-${game.shop}`,
gameAchievements gameAchievements
); );
} catch (error) { } catch (error) {

View File

@@ -1,23 +1,13 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { levelKeys, gamesSublevel } from "@main/level";
import type { GameShop } from "@types";
const selectGameWinePrefix = async ( const selectGameWinePrefix = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, id: number,
objectId: string,
winePrefixPath: string | null winePrefixPath: string | null
) => { ) => {
const gameKey = levelKeys.game(shop, objectId); return gameRepository.update({ id }, { winePrefixPath: winePrefixPath });
const game = await gamesSublevel.get(gameKey);
if (!game) return;
await gamesSublevel.put(gameKey, {
...game,
winePrefixPath: winePrefixPath,
});
}; };
registerEvent("selectGameWinePrefix", selectGameWinePrefix); registerEvent("selectGameWinePrefix", selectGameWinePrefix);

View File

@@ -1,27 +1,25 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { parseExecutablePath } from "../helpers/parse-executable-path"; import { parseExecutablePath } from "../helpers/parse-executable-path";
import { gamesSublevel, levelKeys } from "@main/level";
import type { GameShop } from "@types";
const updateExecutablePath = async ( const updateExecutablePath = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, id: number,
objectId: string,
executablePath: string | null executablePath: string | null
) => { ) => {
const parsedPath = executablePath const parsedPath = executablePath
? parseExecutablePath(executablePath) ? parseExecutablePath(executablePath)
: null; : null;
const gameKey = levelKeys.game(shop, objectId); return gameRepository.update(
{
const game = await gamesSublevel.get(gameKey); id,
if (!game) return; },
{
await gamesSublevel.put(gameKey, { executablePath: parsedPath,
...game, }
executablePath: parsedPath, );
});
}; };
registerEvent("updateExecutablePath", updateExecutablePath); registerEvent("updateExecutablePath", updateExecutablePath);

View File

@@ -1,23 +1,19 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const updateLaunchOptions = async ( const updateLaunchOptions = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, id: number,
objectId: string,
launchOptions: string | null launchOptions: string | null
) => { ) => {
const gameKey = levelKeys.game(shop, objectId); return gameRepository.update(
{
const game = await gamesSublevel.get(gameKey); id,
},
if (game) { {
await gamesSublevel.put(gameKey, {
...game,
launchOptions: launchOptions?.trim() != "" ? launchOptions : null, launchOptions: launchOptions?.trim() != "" ? launchOptions : null,
}); }
} );
}; };
registerEvent("updateLaunchOptions", updateLaunchOptions); registerEvent("updateLaunchOptions", updateLaunchOptions);

View File

@@ -1,17 +1,13 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel } from "@main/level";
const verifyExecutablePathInUse = async ( const verifyExecutablePathInUse = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
executablePath: string executablePath: string
) => { ) => {
for await (const game of gamesSublevel.values()) { return gameRepository.findOne({
if (game.executablePath === executablePath) { where: { executablePath },
return true; });
}
}
return false;
}; };
registerEvent("verifyExecutablePathInUse", verifyExecutablePathInUse); registerEvent("verifyExecutablePathInUse", verifyExecutablePathInUse);

View File

@@ -1,20 +1,17 @@
import { shell } from "electron"; import { shell } from "electron";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { userAuthRepository } from "@main/repository";
import { HydraApi } from "@main/services"; import { HydraApi } from "@main/services";
import { db, levelKeys } from "@main/level";
import type { Auth } from "@types";
const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
const auth = await db.get<string, Auth>(levelKeys.auth, { const userAuth = await userAuthRepository.findOne({ where: { id: 1 } });
valueEncoding: "json",
});
if (!auth) { if (!userAuth) {
return; return;
} }
const paymentToken = await HydraApi.post("/auth/payment", { const paymentToken = await HydraApi.post("/auth/payment", {
refreshToken: auth.refreshToken, refreshToken: userAuth.refreshToken,
}).then((response) => response.accessToken); }).then((response) => response.accessToken);
const params = new URLSearchParams({ const params = new URLSearchParams({

View File

@@ -1,8 +1,7 @@
import { Notification } from "electron"; import { Notification } from "electron";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { userPreferencesRepository } from "@main/repository";
import { t } from "i18next"; import { t } from "i18next";
import { db, levelKeys } from "@main/level";
import type { UserPreferences } from "@types";
const publishNewRepacksNotification = async ( const publishNewRepacksNotification = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
@@ -10,12 +9,9 @@ const publishNewRepacksNotification = async (
) => { ) => {
if (newRepacksCount < 1) return; if (newRepacksCount < 1) return;
const userPreferences = await db.get<string, UserPreferences | null>( const userPreferences = await userPreferencesRepository.findOne({
levelKeys.userPreferences, where: { id: 1 },
{ });
valueEncoding: "json",
}
);
if (userPreferences?.repackUpdatesNotificationsEnabled) { if (userPreferences?.repackUpdatesNotificationsEnabled) {
new Notification({ new Notification({

View File

@@ -7,7 +7,7 @@ import { omit } from "lodash-es";
import axios from "axios"; import axios from "axios";
import { fileTypeFromFile } from "file-type"; import { fileTypeFromFile } from "file-type";
export const patchUserProfile = async (updateProfile: UpdateProfileRequest) => { const patchUserProfile = async (updateProfile: UpdateProfileRequest) => {
return HydraApi.patch<UserProfile>("/profile", updateProfile); return HydraApi.patch<UserProfile>("/profile", updateProfile);
}; };

View File

@@ -1,12 +0,0 @@
import { Theme } from "@types";
import { registerEvent } from "../register-event";
import { themesSublevel } from "@main/level";
const addCustomTheme = async (
_event: Electron.IpcMainInvokeEvent,
theme: Theme
) => {
await themesSublevel.put(theme.id, theme);
};
registerEvent("addCustomTheme", addCustomTheme);

View File

@@ -1,11 +0,0 @@
import { WindowManager } from "@main/services";
import { registerEvent } from "../register-event";
const closeEditorWindow = async (
_event: Electron.IpcMainInvokeEvent,
themeId?: string
) => {
WindowManager.closeEditorWindow(themeId);
};
registerEvent("closeEditorWindow", closeEditorWindow);

View File

@@ -1,8 +0,0 @@
import { themesSublevel } from "@main/level";
import { registerEvent } from "../register-event";
const deleteAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => {
await themesSublevel.clear();
};
registerEvent("deleteAllCustomThemes", deleteAllCustomThemes);

View File

@@ -1,11 +0,0 @@
import { themesSublevel } from "@main/level";
import { registerEvent } from "../register-event";
const deleteCustomTheme = async (
_event: Electron.IpcMainInvokeEvent,
themeId: string
) => {
await themesSublevel.del(themeId);
};
registerEvent("deleteCustomTheme", deleteCustomTheme);

View File

@@ -1,9 +0,0 @@
import { themesSublevel } from "@main/level";
import { registerEvent } from "../register-event";
const getActiveCustomTheme = async () => {
const allThemes = await themesSublevel.values().all();
return allThemes.find((theme) => theme.isActive);
};
registerEvent("getActiveCustomTheme", getActiveCustomTheme);

View File

@@ -1,8 +0,0 @@
import { themesSublevel } from "@main/level";
import { registerEvent } from "../register-event";
const getAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => {
return themesSublevel.values().all();
};
registerEvent("getAllCustomThemes", getAllCustomThemes);

View File

@@ -1,11 +0,0 @@
import { themesSublevel } from "@main/level";
import { registerEvent } from "../register-event";
const getCustomThemeById = async (
_event: Electron.IpcMainInvokeEvent,
themeId: string
) => {
return themesSublevel.get(themeId);
};
registerEvent("getCustomThemeById", getCustomThemeById);

View File

@@ -1,22 +0,0 @@
import { themesSublevel } from "@main/level";
import { registerEvent } from "../register-event";
const toggleCustomTheme = async (
_event: Electron.IpcMainInvokeEvent,
themeId: string,
isActive: boolean
) => {
const theme = await themesSublevel.get(themeId);
if (!theme) {
throw new Error("Theme not found");
}
await themesSublevel.put(themeId, {
...theme,
isActive,
updatedAt: new Date(),
});
};
registerEvent("toggleCustomTheme", toggleCustomTheme);

View File

@@ -1,27 +0,0 @@
import { themesSublevel } from "@main/level";
import { registerEvent } from "../register-event";
import { WindowManager } from "@main/services";
const updateCustomTheme = async (
_event: Electron.IpcMainInvokeEvent,
themeId: string,
code: string
) => {
const theme = await themesSublevel.get(themeId);
if (!theme) {
throw new Error("Theme not found");
}
await themesSublevel.put(themeId, {
...theme,
code,
updatedAt: new Date(),
});
if (theme.isActive) {
WindowManager.mainWindow?.webContents.send("css-injected", code);
}
};
registerEvent("updateCustomTheme", updateCustomTheme);

View File

@@ -1,25 +1,30 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { DownloadManager } from "@main/services"; import { DownloadManager } from "@main/services";
import { GameShop } from "@types"; import { dataSource } from "@main/data-source";
import { downloadsSublevel, levelKeys } from "@main/level"; import { DownloadQueue, Game } from "@main/entity";
const cancelGameDownload = async ( const cancelGameDownload = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const downloadKey = levelKeys.game(shop, objectId); await dataSource.transaction(async (transactionalEntityManager) => {
await DownloadManager.cancelDownload(gameId);
await DownloadManager.cancelDownload(downloadKey); await transactionalEntityManager.getRepository(DownloadQueue).delete({
game: { id: gameId },
});
const download = await downloadsSublevel.get(downloadKey); await transactionalEntityManager.getRepository(Game).update(
{
if (!download) return; id: gameId,
},
await downloadsSublevel.put(downloadKey, { {
...download, status: "removed",
status: "removed", bytesDownloaded: 0,
progress: 0,
}
);
}); });
}; };

View File

@@ -1,27 +1,24 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { DownloadManager } from "@main/services"; import { DownloadManager } from "@main/services";
import { GameShop } from "@types"; import { dataSource } from "@main/data-source";
import { downloadsSublevel, levelKeys } from "@main/level"; import { DownloadQueue, Game } from "@main/entity";
const pauseGameDownload = async ( const pauseGameDownload = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const gameKey = levelKeys.game(shop, objectId); await dataSource.transaction(async (transactionalEntityManager) => {
await DownloadManager.pauseDownload();
const download = await downloadsSublevel.get(gameKey); await transactionalEntityManager.getRepository(DownloadQueue).delete({
game: { id: gameId },
if (download) {
await DownloadManager.pauseDownload(gameKey);
await downloadsSublevel.put(gameKey, {
...download,
status: "paused",
queued: false,
}); });
}
await transactionalEntityManager
.getRepository(Game)
.update({ id: gameId }, { status: "paused" });
});
}; };
registerEvent("pauseGameDownload", pauseGameDownload); registerEvent("pauseGameDownload", pauseGameDownload);

View File

@@ -1,25 +1,17 @@
import { downloadsSublevel, levelKeys } from "@main/level";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { DownloadManager } from "@main/services"; import { DownloadManager } from "@main/services";
import type { GameShop } from "@types"; import { gameRepository } from "@main/repository";
const pauseGameSeed = async ( const pauseGameSeed = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const downloadKey = levelKeys.game(shop, objectId); await gameRepository.update(gameId, {
const download = await downloadsSublevel.get(downloadKey);
if (!download) return;
await downloadsSublevel.put(downloadKey, {
...download,
status: "complete", status: "complete",
shouldSeed: false, shouldSeed: false,
}); });
await DownloadManager.pauseSeeding(downloadKey); await DownloadManager.pauseSeeding(gameId);
}; };
registerEvent("pauseGameSeed", pauseGameSeed); registerEvent("pauseGameSeed", pauseGameSeed);

View File

@@ -1,37 +1,46 @@
import { Not } from "typeorm";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gameRepository } from "../../repository";
import { DownloadManager } from "@main/services"; import { DownloadManager } from "@main/services";
import { downloadsSublevel, levelKeys } from "@main/level"; import { dataSource } from "@main/data-source";
import { GameShop } from "@types"; import { DownloadQueue, Game } from "@main/entity";
const resumeGameDownload = async ( const resumeGameDownload = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const gameKey = levelKeys.game(shop, objectId); const game = await gameRepository.findOne({
where: {
id: gameId,
isDeleted: false,
},
});
const download = await downloadsSublevel.get(gameKey); if (!game) return;
if (download?.status === "paused") { if (game.status === "paused") {
await DownloadManager.pauseDownload(); await dataSource.transaction(async (transactionalEntityManager) => {
await DownloadManager.pauseDownload();
for await (const [key, value] of downloadsSublevel.iterator()) { await transactionalEntityManager
if (value.status === "active" && value.progress !== 1) { .getRepository(Game)
await downloadsSublevel.put(key, { .update({ status: "active", progress: Not(1) }, { status: "paused" });
...value,
status: "paused",
});
}
}
await DownloadManager.resumeDownload(download); await DownloadManager.resumeDownload(game);
await downloadsSublevel.put(gameKey, { await transactionalEntityManager
...download, .getRepository(DownloadQueue)
status: "active", .delete({ game: { id: gameId } });
timestamp: Date.now(),
queued: true, await transactionalEntityManager
.getRepository(DownloadQueue)
.insert({ game: { id: gameId } });
await transactionalEntityManager
.getRepository(Game)
.update({ id: gameId }, { status: "active" });
}); });
} }
}; };

View File

@@ -1,25 +1,29 @@
import { downloadsSublevel, levelKeys } from "@main/level";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gameRepository } from "../../repository";
import { DownloadManager } from "@main/services"; import { DownloadManager } from "@main/services";
import type { GameShop } from "@types"; import { Downloader } from "@shared";
const resumeGameSeed = async ( const resumeGameSeed = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, gameId: number
objectId: string
) => { ) => {
const downloadKey = levelKeys.game(shop, objectId); const game = await gameRepository.findOne({
const download = await downloadsSublevel.get(downloadKey); where: {
id: gameId,
isDeleted: false,
downloader: Downloader.Torrent,
progress: 1,
},
});
if (!download) return; if (!game) return;
await downloadsSublevel.put(downloadKey, { await gameRepository.update(gameId, {
...download,
status: "seeding", status: "seeding",
shouldSeed: true, shouldSeed: true,
}); });
await DownloadManager.resumeSeeding(download); await DownloadManager.resumeSeeding(game);
}; };
registerEvent("resumeGameSeed", resumeGameSeed); registerEvent("resumeGameSeed", resumeGameSeed);

View File

@@ -1,12 +1,13 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import type { Download, StartGameDownloadPayload } from "@types"; import type { StartGameDownloadPayload } from "@types";
import { DownloadManager, HydraApi, logger } from "@main/services"; import { DownloadManager, HydraApi } from "@main/services";
import { Not } from "typeorm";
import { steamGamesWorker } from "@main/workers"; import { steamGamesWorker } from "@main/workers";
import { createGame } from "@main/services/library-sync"; import { createGame } from "@main/services/library-sync";
import { Downloader, DownloadError, steamUrlBuilder } from "@shared"; import { steamUrlBuilder } from "@shared";
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; import { dataSource } from "@main/data-source";
import { AxiosError } from "axios"; import { DownloadQueue, Game } from "@main/entity";
const startGameDownload = async ( const startGameDownload = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
@@ -14,117 +15,85 @@ const startGameDownload = async (
) => { ) => {
const { objectId, title, shop, downloadPath, downloader, uri } = payload; const { objectId, title, shop, downloadPath, downloader, uri } = payload;
const gameKey = levelKeys.game(shop, objectId); return dataSource.transaction(async (transactionalEntityManager) => {
const gameRepository = transactionalEntityManager.getRepository(Game);
const downloadQueueRepository =
transactionalEntityManager.getRepository(DownloadQueue);
await DownloadManager.pauseDownload(); const game = await gameRepository.findOne({
where: {
objectID: objectId,
shop,
},
});
for await (const [key, value] of downloadsSublevel.iterator()) { await DownloadManager.pauseDownload();
if (value.status === "active" && value.progress !== 1) {
await downloadsSublevel.put(key, { await gameRepository.update(
...value, { status: "active", progress: Not(1) },
status: "paused", { status: "paused" }
);
if (game) {
await gameRepository.update(
{
id: game.id,
},
{
status: "active",
progress: 0,
bytesDownloaded: 0,
downloadPath,
downloader,
uri,
isDeleted: false,
}
);
} else {
const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById",
});
const iconUrl = steamGame?.clientIcon
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null;
await gameRepository.insert({
title,
iconUrl,
objectID: objectId,
downloader,
shop,
status: "active",
downloadPath,
uri,
}); });
} }
}
const game = await gamesSublevel.get(gameKey); const updatedGame = await gameRepository.findOne({
where: {
/* Delete any previous download */ objectID: objectId,
await downloadsSublevel.del(gameKey); },
if (game?.isDeleted) {
await gamesSublevel.put(gameKey, {
...game,
isDeleted: false,
});
} else {
const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById",
}); });
const iconUrl = steamGame?.clientIcon await DownloadManager.cancelDownload(updatedGame!.id);
? steamUrlBuilder.icon(objectId, steamGame.clientIcon) await DownloadManager.startDownload(updatedGame!);
: null;
await gamesSublevel.put(gameKey, { await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
title, await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
iconUrl,
objectId,
shop,
remoteId: null,
playTimeInMilliseconds: 0,
lastTimePlayed: null,
isDeleted: false,
});
}
await DownloadManager.cancelDownload(gameKey);
const download: Download = {
shop,
objectId,
status: "active",
progress: 0,
bytesDownloaded: 0,
downloadPath,
downloader,
uri,
folderName: null,
fileSize: null,
shouldSeed: false,
timestamp: Date.now(),
queued: true,
};
try {
await DownloadManager.startDownload(download).then(() => {
return downloadsSublevel.put(gameKey, download);
});
const updatedGame = await gamesSublevel.get(gameKey);
await Promise.all([ await Promise.all([
createGame(updatedGame!).catch(() => {}), createGame(updatedGame!).catch(() => {}),
HydraApi.post( HydraApi.post(
"/games/download", "/games/download",
{ {
objectId, objectId: updatedGame!.objectID,
shop, shop: updatedGame!.shop,
}, },
{ needsAuth: false } { needsAuth: false }
).catch(() => {}), ).catch(() => {}),
]); ]);
});
return { ok: true };
} catch (err: unknown) {
logger.error("Failed to start download", err);
if (err instanceof AxiosError) {
if (err.response?.status === 429 && downloader === Downloader.Gofile) {
return { ok: false, error: DownloadError.GofileQuotaExceeded };
}
if (
err.response?.status === 403 &&
downloader === Downloader.RealDebrid
) {
return {
ok: false,
error: DownloadError.RealDebridAccountNotAuthorized,
};
}
if (downloader === Downloader.TorBox) {
return { ok: false, error: err.response?.data?.detail };
}
}
if (err instanceof Error) {
return { ok: false, error: err.message };
}
return { ok: false };
}
}; };
registerEvent("startGameDownload", startGameDownload); registerEvent("startGameDownload", startGameDownload);

Some files were not shown because too many files have changed in this diff Show More