mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 10:11:46 +00:00
Compare commits
42 Commits
751e7d7daa
...
0747c43128
| Author | SHA1 | Date | |
|---|---|---|---|
| 0747c43128 | |||
| 99be7c2629 | |||
| 200da8dfaa | |||
| 9a218a6516 | |||
| 933ee0ef75 | |||
| 8dd7bcdf97 | |||
| d5c7e0475b | |||
| a5af233683 | |||
| 27e3796622 | |||
| 5843e85c4d | |||
| 92a943c755 | |||
| 58a19cdaa2 | |||
| b1d2112bfc | |||
| d229bc7f00 | |||
| 033a4d3122 | |||
| 8d252b6375 | |||
| 9453c0ca8f | |||
| ce073b30d9 | |||
| 8f63e5e3a3 | |||
| 23853b66c6 | |||
| 62a322f10d | |||
| 9627dd2202 | |||
| 3383926faa | |||
| 8179664064 | |||
| 1a5e417f4f | |||
| ceb6da9bc9 | |||
| fdafb2dd07 | |||
| c3700e0e59 | |||
| 3f1c26f82d | |||
| bb7816815c | |||
| a1773fd992 | |||
| 1671bea942 | |||
| 141ae03208 | |||
| 55c1012cda | |||
| 612c5c89c9 | |||
| 01bbf7e3f7 | |||
| 6a7b7d88de | |||
| d06896450c | |||
| 4a59afc505 | |||
| 7b0d63b6cf | |||
| c734ffe70f | |||
| 3d0ad69ddb |
34
package.json
34
package.json
@ -45,11 +45,11 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vite": "npm:rolldown-vite@7.1.8",
|
||||
"vite": "npm:rolldown-vite@7.3.0",
|
||||
"node-gyp": "11.4.2",
|
||||
"xml2js": "0.6.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"@electron/universal": "3.0.1",
|
||||
"@electron/universal": "3.0.2",
|
||||
"@babel/runtime": "7.28.4"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
@ -66,16 +66,16 @@
|
||||
"@dehoist/romanize-thai": "1.0.0",
|
||||
"@electron-toolkit/tsconfig": "1.0.1",
|
||||
"@electron/remote": "2.1.3",
|
||||
"@ffmpeg.wasm/core-mt": "0.12.0",
|
||||
"@ffmpeg.wasm/core-mt": "0.13.2",
|
||||
"@ffmpeg.wasm/main": "0.12.0",
|
||||
"@floating-ui/dom": "1.7.4",
|
||||
"@foobar404/wave": "2.0.5",
|
||||
"@ghostery/adblocker-electron": "2.11.6",
|
||||
"@ghostery/adblocker-electron-preload": "2.11.6",
|
||||
"@hono/node-server": "1.19.1",
|
||||
"@hono/node-server": "1.19.7",
|
||||
"@hono/node-ws": "1.2.0",
|
||||
"@hono/swagger-ui": "0.5.2",
|
||||
"@hono/zod-openapi": "1.1.0",
|
||||
"@hono/zod-openapi": "1.2.0",
|
||||
"@hono/zod-validator": "0.7.2",
|
||||
"@jellybrick/dbus-next": "0.10.3",
|
||||
"@jellybrick/electron-better-web-request": "1.0.4",
|
||||
@ -105,8 +105,8 @@
|
||||
"fflate": "0.8.2",
|
||||
"filenamify": "6.0.0",
|
||||
"hanja": "1.1.5",
|
||||
"happy-dom": "18.0.1",
|
||||
"hono": "4.9.6",
|
||||
"happy-dom": "20.0.2",
|
||||
"hono": "4.10.3",
|
||||
"howler": "2.2.4",
|
||||
"html-to-text": "9.0.5",
|
||||
"i18next": "25.5.2",
|
||||
@ -131,11 +131,11 @@
|
||||
"solid-transition-group": "0.3.0",
|
||||
"tiny-pinyin": "1.3.2",
|
||||
"tinyld": "1.3.4",
|
||||
"virtua": "0.42.3",
|
||||
"virtua": "0.48.2",
|
||||
"vudio": "2.1.1",
|
||||
"x11": "2.3.0",
|
||||
"youtubei.js": "^16.0.1",
|
||||
"zod": "4.1.5"
|
||||
"zod": "4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/tsconfig": "1.0.1",
|
||||
@ -153,12 +153,12 @@
|
||||
"builtin-modules": "5.0.0",
|
||||
"cross-env": "10.0.0",
|
||||
"del-cli": "6.0.0",
|
||||
"discord-api-types": "0.38.23",
|
||||
"electron": "38.2.0",
|
||||
"discord-api-types": "0.38.37",
|
||||
"electron": "38.7.2",
|
||||
"electron-builder": "26.0.12",
|
||||
"electron-builder-squirrel-windows": "26.0.12",
|
||||
"electron-devtools-installer": "4.0.0",
|
||||
"electron-vite": "4.0.0",
|
||||
"electron-vite": "4.0.1",
|
||||
"eslint": "9.35.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||
@ -166,14 +166,14 @@
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-solid": "0.14.5",
|
||||
"glob": "11.0.3",
|
||||
"glob": "11.1.0",
|
||||
"node-gyp": "11.4.2",
|
||||
"playwright": "1.55.0",
|
||||
"ts-morph": "27.0.0",
|
||||
"typescript": "5.9.2",
|
||||
"playwright": "1.55.1",
|
||||
"ts-morph": "27.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.43.0",
|
||||
"utf-8-validate": "6.0.5",
|
||||
"vite": "npm:rolldown-vite@7.1.8",
|
||||
"vite": "npm:rolldown-vite@7.3.0",
|
||||
"vite-plugin-inspect": "11.3.3",
|
||||
"vite-plugin-resolve": "2.5.2",
|
||||
"vite-plugin-solid": "2.11.8",
|
||||
|
||||
868
pnpm-lock.yaml
generated
868
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Ha fallat l'execució de l'extensió {{pluginName}}::{{contextName}}",
|
||||
"execute-failed": "Error en l'execució de l'extensió {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "L'extensió {{pluginName}}::{{contextName}} s'ha executat als {{ms}}ms",
|
||||
"initialize-failed": "Ha fallat la inicialització de l'extensió \"{{pluginName}}\"",
|
||||
"load-all": "Carregant totes les extensions",
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Activar el tema en la barra de progrés"
|
||||
},
|
||||
"name": "Tema de color de l'àlbum"
|
||||
},
|
||||
@ -462,8 +463,8 @@
|
||||
"label": "Text d'estat",
|
||||
"submenu": {
|
||||
"artist": "Escoltant {artist}",
|
||||
"title": "Escoltant {song title}",
|
||||
"pear-desktop": "Escoltant Pear Desktop"
|
||||
"pear-desktop": "Escoltant Pear Desktop",
|
||||
"title": "Escoltant {song title}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Suchleisten-Design aktivieren"
|
||||
},
|
||||
"name": "Thema aus Albumfarbe"
|
||||
},
|
||||
@ -606,7 +607,7 @@
|
||||
"permission": {
|
||||
"all": "Gästen erlauben, Wiederhabeliste und Player zu bedienen",
|
||||
"host-only": "Nur der Host kann die Playlist und den Player kontrollieren",
|
||||
"playlist": "Gäste das Kontrollieren der Playlist erlauben"
|
||||
"playlist": "Gästen das Kontrollieren der Playlist erlauben"
|
||||
},
|
||||
"set-permission": "Kontrollberechtigung ändern",
|
||||
"status": {
|
||||
|
||||
@ -323,6 +323,22 @@
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
},
|
||||
"https": {
|
||||
"label": "HTTPS & Certificates",
|
||||
"submenu": {
|
||||
"enable-https": {
|
||||
"label": "Enable HTTPS"
|
||||
},
|
||||
"cert": {
|
||||
"label": "Certificate file (.crt/.pem)",
|
||||
"dialogTitle": "Select HTTPS certificate file"
|
||||
},
|
||||
"key": {
|
||||
"label": "Private key file (.key/.pem)",
|
||||
"dialogTitle": "Select HTTPS private key file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "API Server [Beta]",
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Error al ejecutar el complemento {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Complemento {{pluginName}}::{{contextName}} Ejecutó en {{ms}}ms",
|
||||
"executed-at-ms": "Complemento {{pluginName}}::{{contextName}} se ejecutó en {{ms}}ms",
|
||||
"initialize-failed": "Error al inicializar el complemento \"{{pluginName}}\"",
|
||||
"load-all": "Cargando todos los complementos",
|
||||
"load-failed": "Error al cargar el complemento \"{{pluginName}}\"",
|
||||
"loaded": "Complementos \"{{pluginName}}\" cargado",
|
||||
"loaded": "Complementos \"{{pluginName}}\" cargados",
|
||||
"unload-failed": "No se ha podido descargar el complemento \"{{pluginName}}\"",
|
||||
"unloaded": "Complemento \"{{pluginName}}\" descargado"
|
||||
}
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Habilitar temas a la barra de búsqueda"
|
||||
},
|
||||
"name": "Tema de color del álbum"
|
||||
},
|
||||
@ -462,8 +463,8 @@
|
||||
"label": "Texto de estado",
|
||||
"submenu": {
|
||||
"artist": "Escuchando a {artist}",
|
||||
"title": "Escuchando {song title}",
|
||||
"pear-desktop": "Escuchando Pear Desktop"
|
||||
"pear-desktop": "Escuchando Pear Desktop",
|
||||
"title": "Escuchando {song title}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -153,7 +153,7 @@
|
||||
"custom-window-title": {
|
||||
"label": "Titre de fenêtre personnalisé",
|
||||
"prompt": {
|
||||
"label": "Entrés un titre de fenêtre : (Laissé vide pour déactiver)",
|
||||
"label": "Entrer un titre de fenêtre : (Laissé vide pour désactiver)",
|
||||
"placeholder": "Exemple : Pear Desktop"
|
||||
}
|
||||
},
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Activer le thème sur la barre de progression"
|
||||
},
|
||||
"name": "Thème de couleur d'album"
|
||||
},
|
||||
@ -290,7 +291,7 @@
|
||||
"description": "Ajout de la prise en charge de Pear Desktop pour le widget Amuse now playing de 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Le serveur API Amuse est en cours d'exécution. Envoyez une requête GET /query pour obtenir des informations sur la chanson."
|
||||
"query": "Le serveur API d'Amuse est en cours d'exécution. GET /query pour obtenir des informations sur les chansons."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
@ -302,7 +303,7 @@
|
||||
"deny": "Interdire"
|
||||
},
|
||||
"message": "Autoriser {{ID}} ({{origin}}) à accéder à l'API ?",
|
||||
"title": "Requête d'autorisation d'API"
|
||||
"title": "Demande d'autorisation à l'API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@ -310,7 +311,7 @@
|
||||
"label": "Plan d'autorisation",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autoriser à la première requête"
|
||||
"label": "Autoriser lors de la première requête"
|
||||
},
|
||||
"none": {
|
||||
"label": "Pas d'autorisation"
|
||||
@ -331,7 +332,7 @@
|
||||
"title": "Nom d'hôte"
|
||||
},
|
||||
"port": {
|
||||
"label": "Entrez le port du serveur de l'API :",
|
||||
"label": "Entrez le port du serveur API :",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
@ -392,7 +393,7 @@
|
||||
"toast": {
|
||||
"caption-changed": "Sous-titres changés en {{language}}",
|
||||
"caption-disabled": "Sous-titres désactivés",
|
||||
"no-captions": "Aucun sous-titre disponible pour cette chanson"
|
||||
"no-captions": "Aucun sous-titres disponibles pour cette chanson"
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
@ -462,8 +463,8 @@
|
||||
"label": "Texte d'état",
|
||||
"submenu": {
|
||||
"artist": "Écoute {artiste}",
|
||||
"title": "Écoute {titre de la chanson}",
|
||||
"pear-desktop": "Écoute Pear Desktop"
|
||||
"pear-desktop": "Écoute Pear Desktop",
|
||||
"title": "Écoute {titre de la chanson}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -636,7 +637,7 @@
|
||||
"name": "Navigation",
|
||||
"templates": {
|
||||
"back": {
|
||||
"title": "Revenir à la page précédente"
|
||||
"title": "Aller à la page précédente"
|
||||
},
|
||||
"forward": {
|
||||
"title": "Aller à la page suivante"
|
||||
@ -909,7 +910,7 @@
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"unobtrusive-player": {
|
||||
"description": "Empêche le lecteur de s'afficher quand un chanson est en lecture",
|
||||
"description": "Empêche le lecteur de s'afficher quand une chanson est en cours de lecture",
|
||||
"name": "Lecteur Non-Intrusif"
|
||||
},
|
||||
"video-toggle": {
|
||||
|
||||
@ -44,25 +44,38 @@
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "მენიუ დამალულია, გამოიყენეთ 'Alt', რათა გამოაჩინოთ ის (ან 'Escape' თუ იყენებთ აპლიკაციის შიგნითა მენიუს)"
|
||||
"detail": "მენიუ დამალულია, გამოიყენეთ 'Alt', რათა გამოაჩინოთ ის (ან 'Escape' თუ იყენებთ აპლიკაციის შიგნითა მენიუს)",
|
||||
"message": "მენიუს დამალვა ჩართულია",
|
||||
"title": "მენიუს დამალვა ჩართულია"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "მოგვიანებით"
|
||||
}
|
||||
"later": "მოგვიანებით",
|
||||
"restart-now": "გადატვირთვა ახლავე"
|
||||
},
|
||||
"detail": "„{{pluginName}}“ დანამატის ძალაში შესასვლელად გადატვირთვა საჭიროა",
|
||||
"message": "\"{{pluginName}}\" საჭიროებს გადატვირთვას",
|
||||
"title": "საჭიროებს გადატვირთვას"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "გასვლა",
|
||||
"relaunch": "თავიდან გაშვება",
|
||||
"wait": "მოცდა"
|
||||
}
|
||||
},
|
||||
"detail": "ბოდიშს გიხდით მოუხერხელობისათვის! გთხოვთ აირჩიეთ რა უნდა გაკეთდეს:",
|
||||
"message": "აპლიკაცია არ პასუხობს",
|
||||
"title": "ფანჯარა არ პასუხობს"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "განახლებების გამორთვა",
|
||||
"download": "გადმოწერა",
|
||||
"ok": "დიახ"
|
||||
}
|
||||
},
|
||||
"detail": "ახალი ვერსიაა ხელმისაწვდომი, მისი ჩამოტვირთვა შესაძლებელია {{downloadLink}}-დან",
|
||||
"message": "ახალი ვერსია ხელმისაწვდომია",
|
||||
"title": "განახლება ხელმისაწვდომია"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@ -70,19 +83,63 @@
|
||||
"navigation": {
|
||||
"label": "ნავიგაცია",
|
||||
"submenu": {
|
||||
"quit": "გასვლა"
|
||||
"copy-current-url": "მიმდინარე URL-ის დაკოპირება",
|
||||
"go-back": "უკან დაბრუნება",
|
||||
"go-forward": "წინ გადასვლა",
|
||||
"quit": "გასვლა",
|
||||
"restart": "აპლიკაციის გადატვირთვა"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "მორგება",
|
||||
"submenu": {
|
||||
"language": {
|
||||
"label": "ენა"
|
||||
"advanced-options": {
|
||||
"label": "გაფართოებული პარამეტრები",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "აპლიკაციის ქეშის გადატვირთვა როცა აპლიკაცია დაიწყება",
|
||||
"disable-hardware-acceleration": "აპარატურული აჩქარების გამორთვა",
|
||||
"edit-config-json": "config.json-ის რედაქტირება",
|
||||
"override-user-agent": "მომხმარებლის აგენტის შეცვლა",
|
||||
"restart-on-config-changes": "გადატვირთვა კონფიგურაციის ცვლილებების დროს",
|
||||
"set-proxy": {
|
||||
"label": "პროქსის დაყენება",
|
||||
"prompt": {
|
||||
"label": "შეიყვანეთ პროქსის მისამართი: (გამორთვისთვის დატოვეთ ცარიელი)",
|
||||
"placeholder": "მაგალითი: SOCKS5://127.0.0.1:9999",
|
||||
"title": "პროქსის დაყენება"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "DevTools-ის გადართვა"
|
||||
}
|
||||
},
|
||||
"always-on-top": "მუდამ ზემოთ",
|
||||
"auto-update": "ავტომატური განახლება",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "მენიუ შემდეგი გაშვებისას დაიმალება, მის საჩვენებლად გამოიყენეთ [Alt] (ან თუ აპლიკაციის მენიუს იყენებთ, უკან დააწკაპუნეთ [`])",
|
||||
"title": "მენიუს დამალვა ჩართულია"
|
||||
},
|
||||
"label": "მენიუს დამალვა"
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "გადატვირთვის შემდეგ ენა შეიცვლება",
|
||||
"title": "ენა შეიცვალა"
|
||||
},
|
||||
"label": "ენა",
|
||||
"submenu": {
|
||||
"to-help-translate": "გსურთ დაგვეხმაროთ თარგმნაში? დააწკაპუნეთ აქ"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "აპლიკაციის თავიდან გაშვებისას ბოლო სიმღერა დაუკრას",
|
||||
"single-instance-lock": "ერთჯერადი ინსტანციის საკეტი",
|
||||
"start-at-login": "შესვლაზე დაწყება",
|
||||
"starting-page": {
|
||||
"label": "საწყისი გვერდი",
|
||||
"unset": "მოხსნა"
|
||||
},
|
||||
"tray": {
|
||||
"label": "უჯრა",
|
||||
"submenu": {
|
||||
"disabled": "გამორთულია"
|
||||
}
|
||||
@ -162,11 +219,15 @@
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "სრული ეკრანის გამოყენება"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "გარემოს რეჟიმი"
|
||||
},
|
||||
"amuse": {
|
||||
"name": "Amuse"
|
||||
"name": "გაკვირვება"
|
||||
},
|
||||
"api-server": {
|
||||
"dialog": {
|
||||
@ -267,6 +328,13 @@
|
||||
"status": {
|
||||
"disconnected": "გათიშული"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"closed": "Music Together-ის ორგანიზატორი დაიხურა",
|
||||
"disconnected": "Music Together-ის კავშირი გათიშულია",
|
||||
"host-failed": "Music Together-ის გამოცხადება ვერ მოხერხდა",
|
||||
"id-copied": "გამოსაცხადებელი ID დაკოპირებულია ბუფერში",
|
||||
"id-copy-failed": "გამოსაცხადებელი ID-ის ვერ დაკოპირდა ბუფერში"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
@ -309,6 +377,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video-toggle": {
|
||||
"menu": {
|
||||
"mode": {
|
||||
"label": "რეჟიმი",
|
||||
"submenu": {
|
||||
"custom": "მორგებული გადამრთველი",
|
||||
"disabled": "გამორთულია",
|
||||
"native": "ადგილობრივი გადართვა"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "ვიდეოს გადართვა",
|
||||
"templates": {
|
||||
"button-song": "სიმღერა",
|
||||
"button-video": "ვიდეო"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "პლეიერს ვიზუალიზატორს უმატებს",
|
||||
"menu": {
|
||||
"visualizer-type": "ვიზუალიზატორის ტიპი"
|
||||
},
|
||||
"name": "ვიზუალიზატორი"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
src/i18n/resources/kmr.json
Normal file
1
src/i18n/resources/kmr.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -150,6 +150,13 @@
|
||||
"visual-tweaks": {
|
||||
"label": "भिजुअल ट्वीक्स",
|
||||
"submenu": {
|
||||
"custom-window-title": {
|
||||
"label": "अनुकूलन विन्डो शीर्षक",
|
||||
"prompt": {
|
||||
"label": "अनुकूलन विन्डो शीर्षक प्रविष्ट गर्नुहोस्: (असक्षम पार्न खाली छोड्नुहोस्)",
|
||||
"placeholder": "उदाहरण: पियर डेस्कटप"
|
||||
}
|
||||
},
|
||||
"like-buttons": {
|
||||
"default": "पूर्वनिर्धारित",
|
||||
"force-show": "देखाउनुहोस",
|
||||
@ -179,7 +186,7 @@
|
||||
"plugins": {
|
||||
"enabled": "सक्षम गरियो",
|
||||
"label": "प्लगइनहरू",
|
||||
"new": "NEW"
|
||||
"new": "नयाँ"
|
||||
},
|
||||
"view": {
|
||||
"label": "हेर्नुहोस्",
|
||||
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Ativar personalização da barra de progresso"
|
||||
},
|
||||
"name": "Tema da cor do álbum"
|
||||
},
|
||||
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Ativar temas na barra de reprodução"
|
||||
},
|
||||
"name": "Tema de cores do álbum"
|
||||
},
|
||||
@ -462,8 +463,8 @@
|
||||
"label": "Texto de estado",
|
||||
"submenu": {
|
||||
"artist": "A ouvir {artist}",
|
||||
"title": "A ouvir {song title}",
|
||||
"pear-desktop": "A reproduzir Pear Desktop"
|
||||
"pear-desktop": "A reproduzir Pear Desktop",
|
||||
"title": "A ouvir {song title}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
15
src/i18n/resources/qu.json
Normal file
15
src/i18n/resources/qu.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Pluginta mana ruwayta atirqanchu {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Plugin nisqa {{pluginName}}::{{contextName}} ejecutado en {{ms}}ms",
|
||||
"initialize-failed": "Plugin qallariyta mana atirqanchu \"{{pluginName}}\"",
|
||||
"load-all": "Llapanta cargaspa",
|
||||
"load-failed": "Pluginta mana kargayta atirqanchu \"{{pluginName}}\"",
|
||||
"loaded": "Plugin nisqa \"{{pluginName}}\" cargado",
|
||||
"unload-failed": "Pluginta mana uraykachiyta atirqanchu \"{{pluginName}}\""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Povoliť farebnú tému aj v seekbare"
|
||||
},
|
||||
"name": "Farebná téma albumu"
|
||||
},
|
||||
|
||||
@ -3,7 +3,56 @@
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Dështoi në ekzekutimin e plugin-it {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Shtojca {{pluginName}}::{{contextName}} u ekzekutua në {{ms}}ms"
|
||||
"executed-at-ms": "Shtojca {{pluginName}}::{{contextName}} u ekzekutua në {{ms}}ms",
|
||||
"initialize-failed": "Dështoi ekzekutimi i plugin-it \"{{pluginName}}\"",
|
||||
"load-all": "Duke ngarkuar të gjithe plugins",
|
||||
"load-failed": "Dështoi në ngarkimin e plugin-it \"{{pluginName}}\"",
|
||||
"loaded": "Plugin \"{{pluginName}}\" u ngarkua",
|
||||
"unload-failed": "Shkarkimi i plugin-it \"{{pluginName}}\" dështoi",
|
||||
"unloaded": "Plugin \"{{pluginName}}\" u shkarkua"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"name": "Emri i gjuhës në anglisht. p.sh. japonisht, koreanisht, anglisht, rusisht"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "Ngarkimi përfundoi. DevTools u hap."
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n i ngarkuar"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "U mor komanda mbi protokollin: \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "Skedari CSS \"{{cssFile}}\" nuk ekziston, duke u injoruar"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Gabim i Papërgjigjes!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Duke pastruar memorien e përkohshme të aplikacionit"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Dritarja u përpoq të renderohej jashtë ekranit, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Menuja është e fshehur, përdorni 'Alt' për ta shfaqur (ose 'Escape' nëse përdorni Menunë brenda aplikacionit)",
|
||||
"message": "Fshehja e menusë është aktivizuar",
|
||||
"title": "Fshih Menunë Aktivizuar"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Më vonë",
|
||||
"restart-now": "Rinisni Tani"
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" kërkon një restart që të hyjë në fuqi",
|
||||
"message": "\"{{pluginName}}\" kerkon restart"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@
|
||||
"copy-current-url": "Kopiera nuvarande länk",
|
||||
"go-back": "Gå tillbaka",
|
||||
"go-forward": "Gå framåt",
|
||||
"quit": "Avsluta",
|
||||
"quit": "Stäng",
|
||||
"restart": "Starta om appen"
|
||||
}
|
||||
},
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}} %"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Aktivera temaanpassning av uppspelningsreglaget"
|
||||
},
|
||||
"name": "Albumfärgtema"
|
||||
},
|
||||
@ -462,8 +463,8 @@
|
||||
"label": "Statusmeddelande",
|
||||
"submenu": {
|
||||
"artist": "Lyssnar på {artist}",
|
||||
"title": "Lyssnar på {song title}",
|
||||
"pear-desktop": "Lyssnar på Pear Desktop"
|
||||
"pear-desktop": "Lyssnar på Pear Desktop",
|
||||
"title": "Lyssnar på {song title}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "Arama çubuğu temalarını etkinleştir"
|
||||
},
|
||||
"name": "Albüm Renk Teması"
|
||||
},
|
||||
@ -462,8 +463,8 @@
|
||||
"label": "Durum metni",
|
||||
"submenu": {
|
||||
"artist": "{artist} Dinleniyor",
|
||||
"title": "{song title} Dinleniyor",
|
||||
"pear-desktop": "Pear Desktop Dinleniyor"
|
||||
"pear-desktop": "Pear Desktop Dinleniyor",
|
||||
"title": "{song title} Dinleniyor"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -237,7 +237,8 @@
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-seekbar": "啟用進度條主題樣式"
|
||||
},
|
||||
"name": "隨歌曲色調變更主題"
|
||||
},
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import { createServer as createHttpServer } from 'node:http';
|
||||
import { createServer as createHttpsServer } from 'node:https';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
import { jwt } from 'hono/jwt';
|
||||
import { OpenAPIHono as Hono } from '@hono/zod-openapi';
|
||||
import { cors } from 'hono/cors';
|
||||
@ -48,22 +52,26 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
(newVolumeState: VolumeState) => (this.volumeState = newVolumeState),
|
||||
);
|
||||
|
||||
this.run(config.hostname, config.port);
|
||||
this.run(config);
|
||||
},
|
||||
stop() {
|
||||
this.end();
|
||||
},
|
||||
onConfigChange(config) {
|
||||
const old = this.oldConfig;
|
||||
if (
|
||||
this.oldConfig?.hostname === config.hostname &&
|
||||
this.oldConfig?.port === config.port
|
||||
old?.hostname === config.hostname &&
|
||||
old?.port === config.port &&
|
||||
old?.useHttps === config.useHttps &&
|
||||
old?.certPath === config.certPath &&
|
||||
old?.keyPath === config.keyPath
|
||||
) {
|
||||
this.oldConfig = config;
|
||||
return;
|
||||
}
|
||||
|
||||
this.end();
|
||||
this.run(config.hostname, config.port);
|
||||
this.run(config);
|
||||
this.oldConfig = config;
|
||||
},
|
||||
|
||||
@ -153,15 +161,30 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
|
||||
this.injectWebSocket = ws.injectWebSocket.bind(this);
|
||||
},
|
||||
run(hostname, port) {
|
||||
run(config) {
|
||||
if (!this.app) return;
|
||||
|
||||
try {
|
||||
this.server = serve({
|
||||
fetch: this.app.fetch.bind(this.app),
|
||||
port,
|
||||
hostname,
|
||||
});
|
||||
const serveOptions =
|
||||
config.useHttps && config.certPath && config.keyPath
|
||||
? {
|
||||
fetch: this.app.fetch.bind(this.app),
|
||||
port: config.port,
|
||||
hostname: config.hostname,
|
||||
createServer: createHttpsServer,
|
||||
serverOptions: {
|
||||
key: readFileSync(config.keyPath),
|
||||
cert: readFileSync(config.certPath),
|
||||
},
|
||||
}
|
||||
: {
|
||||
fetch: this.app.fetch.bind(this.app),
|
||||
port: config.port,
|
||||
hostname: config.hostname,
|
||||
createServer: createHttpServer,
|
||||
};
|
||||
|
||||
this.server = serve(serveOptions);
|
||||
|
||||
if (this.injectWebSocket && this.server) {
|
||||
this.injectWebSocket(this.server);
|
||||
|
||||
@ -411,6 +411,26 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
nextSongInfo: createRoute({
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/queue/next`,
|
||||
summary: 'get next song info',
|
||||
description:
|
||||
'Get information about the next song in the queue (relative index +1)',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: SongInfoSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
204: {
|
||||
description: 'No next song in queue',
|
||||
},
|
||||
},
|
||||
}),
|
||||
queueInfo: createRoute({
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/queue`,
|
||||
@ -748,6 +768,63 @@ export const register = (
|
||||
app.openapi(routes.oldQueueInfo, queueInfo);
|
||||
app.openapi(routes.queueInfo, queueInfo);
|
||||
|
||||
app.openapi(routes.nextSongInfo, async (ctx) => {
|
||||
const queueResponsePromise = new Promise<QueueResponse>((resolve) => {
|
||||
ipcMain.once('peard:get-queue-response', (_, queue: QueueResponse) => {
|
||||
return resolve(queue);
|
||||
});
|
||||
|
||||
controller.requestQueueInformation();
|
||||
});
|
||||
|
||||
const queue = await queueResponsePromise;
|
||||
|
||||
if (!queue?.items || queue.items.length === 0) {
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
}
|
||||
|
||||
// Find the currently selected song
|
||||
const currentIndex = queue.items.findIndex((item) => {
|
||||
const renderer =
|
||||
item.playlistPanelVideoRenderer ||
|
||||
item.playlistPanelVideoWrapperRenderer?.primaryRenderer
|
||||
?.playlistPanelVideoRenderer;
|
||||
return renderer?.selected === true;
|
||||
});
|
||||
|
||||
// Get the next song (currentIndex + 1)
|
||||
const nextIndex = currentIndex + 1;
|
||||
if (nextIndex >= queue.items.length) {
|
||||
// No next song available
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
}
|
||||
|
||||
const nextItem = queue.items[nextIndex];
|
||||
const nextRenderer =
|
||||
nextItem.playlistPanelVideoRenderer ||
|
||||
nextItem.playlistPanelVideoWrapperRenderer?.primaryRenderer
|
||||
?.playlistPanelVideoRenderer;
|
||||
|
||||
if (!nextRenderer) {
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
}
|
||||
|
||||
// Extract relevant information similar to SongInfo format
|
||||
const nextSongInfo = {
|
||||
title: nextRenderer.title?.runs?.[0]?.text,
|
||||
videoId: nextRenderer.videoId,
|
||||
thumbnail: nextRenderer.thumbnail,
|
||||
lengthText: nextRenderer.lengthText,
|
||||
shortBylineText: nextRenderer.shortBylineText,
|
||||
};
|
||||
|
||||
ctx.status(200);
|
||||
return ctx.json(nextSongInfo);
|
||||
});
|
||||
|
||||
app.openapi(routes.addSongToQueue, (ctx) => {
|
||||
const { videoId, insertPosition } = ctx.req.valid('json');
|
||||
controller.addSongToQueue(videoId, insertPosition);
|
||||
|
||||
@ -17,6 +17,6 @@ export type BackendType = {
|
||||
injectWebSocket?: (server: ReturnType<typeof serve>) => void;
|
||||
|
||||
init: (ctx: BackendContext<APIServerConfig>) => void;
|
||||
run: (hostname: string, port: number) => void;
|
||||
run: (config: APIServerConfig) => void;
|
||||
end: () => void;
|
||||
};
|
||||
|
||||
@ -11,6 +11,9 @@ export interface APIServerConfig {
|
||||
secret: string;
|
||||
|
||||
authorizedClients: string[];
|
||||
useHttps: boolean;
|
||||
certPath: string;
|
||||
keyPath: string;
|
||||
}
|
||||
|
||||
export const defaultAPIServerConfig: APIServerConfig = {
|
||||
@ -21,4 +24,7 @@ export const defaultAPIServerConfig: APIServerConfig = {
|
||||
secret: Date.now().toString(36),
|
||||
|
||||
authorizedClients: [],
|
||||
useHttps: false,
|
||||
certPath: '',
|
||||
keyPath: '',
|
||||
};
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { dialog } from 'electron';
|
||||
import prompt from 'custom-electron-prompt';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
@ -93,5 +94,51 @@ export const onMenu = async ({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('plugins.api-server.menu.https.label'),
|
||||
type: 'submenu',
|
||||
submenu: [
|
||||
{
|
||||
label: t('plugins.api-server.menu.https.submenu.enable-https.label'),
|
||||
type: 'checkbox',
|
||||
checked: config.useHttps,
|
||||
click(menuItem) {
|
||||
setConfig({ ...config, useHttps: menuItem.checked });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('plugins.api-server.menu.https.submenu.cert.label'),
|
||||
type: 'normal',
|
||||
async click() {
|
||||
const config = await getConfig();
|
||||
const result = await dialog.showOpenDialog(window, {
|
||||
title: t(
|
||||
'plugins.api-server.menu.https.submenu.cert.dialogTitle',
|
||||
),
|
||||
filters: [{ name: 'Certificate', extensions: ['crt', 'pem'] }],
|
||||
properties: ['openFile'],
|
||||
});
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
setConfig({ ...config, certPath: result.filePaths[0] });
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('plugins.api-server.menu.https.submenu.key.label'),
|
||||
type: 'normal',
|
||||
async click() {
|
||||
const config = await getConfig();
|
||||
const result = await dialog.showOpenDialog(window, {
|
||||
title: t('plugins.api-server.menu.https.submenu.key.dialogTitle'),
|
||||
filters: [{ name: 'Private Key', extensions: ['key', 'pem'] }],
|
||||
properties: ['openFile'],
|
||||
});
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
setConfig({ ...config, keyPath: result.filePaths[0] });
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@ -9,9 +9,11 @@ import delay from 'delay';
|
||||
import type { Permission, Profile, VideoData } from './types';
|
||||
|
||||
export type ConnectionEventMap = {
|
||||
CLEAR_QUEUE: {};
|
||||
ADD_SONGS: { videoList: VideoData[]; index?: number };
|
||||
REMOVE_SONG: { index: number };
|
||||
MOVE_SONG: { fromIndex: number; toIndex: number };
|
||||
SET_INDEX: { index: number };
|
||||
IDENTIFY: { profile: Profile } | undefined;
|
||||
SYNC_PROFILE: { profiles: Record<string, Profile> } | undefined;
|
||||
SYNC_QUEUE: { videoList: VideoData[] } | undefined;
|
||||
@ -171,9 +173,10 @@ export class Connection {
|
||||
public async broadcast<Event extends keyof ConnectionEventMap>(
|
||||
type: Event,
|
||||
payload: ConnectionEventMap[Event],
|
||||
after?: ConnectionEventUnion[],
|
||||
) {
|
||||
await Promise.all(
|
||||
this.getConnections().map((conn) => conn.send({ type, payload })),
|
||||
this.getConnections().map((conn) => conn.send({ type, payload, after })),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -215,6 +215,25 @@ export default createPlugin<
|
||||
this.ignoreChange = true;
|
||||
|
||||
switch (event.type) {
|
||||
case 'CLEAR_QUEUE': {
|
||||
if (conn && this.permission === 'host-only') {
|
||||
await this.connection?.broadcast('SYNC_QUEUE', {
|
||||
videoList: this.queue?.videoList ?? [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.queue?.clear();
|
||||
await this.connection?.broadcast('CLEAR_QUEUE', {});
|
||||
break;
|
||||
}
|
||||
case 'SET_INDEX': {
|
||||
this.queue?.setIndex(event.payload.index);
|
||||
await this.connection?.broadcast('SET_INDEX', {
|
||||
index: event.payload.index,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'ADD_SONGS': {
|
||||
if (conn && this.permission === 'host-only') {
|
||||
await this.connection?.broadcast('SYNC_QUEUE', {
|
||||
@ -234,7 +253,15 @@ export default createPlugin<
|
||||
await this.connection?.broadcast('ADD_SONGS', {
|
||||
...event.payload,
|
||||
videoList,
|
||||
});
|
||||
},
|
||||
event.after,
|
||||
);
|
||||
|
||||
const afterevent = event.after?.at(0);
|
||||
if (afterevent?.type === 'SET_INDEX') {
|
||||
this.queue?.setIndex(afterevent.payload.index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
@ -385,6 +412,16 @@ export default createPlugin<
|
||||
const queueListener = async (event: ConnectionEventUnion) => {
|
||||
this.ignoreChange = true;
|
||||
switch (event.type) {
|
||||
case 'CLEAR_QUEUE': {
|
||||
await this.connection?.broadcast('CLEAR_QUEUE', {});
|
||||
break;
|
||||
}
|
||||
case 'SET_INDEX': {
|
||||
await this.connection?.broadcast('SET_INDEX', {
|
||||
index: event.payload.index,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'ADD_SONGS': {
|
||||
await this.connection?.broadcast('ADD_SONGS', {
|
||||
...event.payload,
|
||||
@ -392,7 +429,9 @@ export default createPlugin<
|
||||
...it,
|
||||
ownerId: it.ownerId ?? this.connection!.id,
|
||||
})),
|
||||
});
|
||||
},
|
||||
event.after,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
@ -420,6 +459,14 @@ export default createPlugin<
|
||||
const listener = async (event: ConnectionEventUnion) => {
|
||||
this.ignoreChange = true;
|
||||
switch (event.type) {
|
||||
case 'CLEAR_QUEUE': {
|
||||
this.queue?.clear();
|
||||
break;
|
||||
}
|
||||
case 'SET_INDEX': {
|
||||
this.queue?.setIndex(event.payload.index);
|
||||
break;
|
||||
}
|
||||
case 'ADD_SONGS': {
|
||||
const videoList: VideoData[] = event.payload.videoList.map(
|
||||
(it) => ({
|
||||
@ -429,6 +476,13 @@ export default createPlugin<
|
||||
);
|
||||
|
||||
await this.queue?.addVideos(videoList, event.payload.index);
|
||||
|
||||
const afterevent = event.after?.at(0);
|
||||
if (afterevent?.type === 'SET_INDEX') {
|
||||
this.queue?.setIndex(afterevent.payload.index);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
|
||||
@ -314,6 +314,11 @@ export class Queue {
|
||||
if (!this.internalDispatch) {
|
||||
if (event.type === 'CLEAR') {
|
||||
this.ignoreFlag = true;
|
||||
this.broadcast({
|
||||
type: 'CLEAR_QUEUE',
|
||||
payload: {},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (event.type === 'ADD_ITEMS') {
|
||||
if (this.ignoreFlag) {
|
||||
@ -347,7 +352,7 @@ export class Queue {
|
||||
},
|
||||
after: [
|
||||
{
|
||||
type: 'SYNC_PROGRESS',
|
||||
type: 'SET_INDEX',
|
||||
payload: {
|
||||
index,
|
||||
},
|
||||
|
||||
@ -16,4 +16,5 @@ export const startingPages: Record<string, string> = {
|
||||
'Uploaded Songs': 'FEmusic_library_privately_owned_tracks',
|
||||
'Uploaded Albums': 'FEmusic_library_privately_owned_releases',
|
||||
'Uploaded Artists': 'FEmusic_library_privately_owned_artists',
|
||||
'Mixed for you': 'FEmusic_mixed_for_you',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user