diff --git a/config.js b/config.js index c1f20b5..76f34bc 100644 --- a/config.js +++ b/config.js @@ -153,7 +153,7 @@ const config = { // Command permission settings command: { disableCommand: [], // Disabled commands, all enabled by default - adminCommand: ['blacklist', 'language','server', 'status'], // Admin commands, only Admin role user can use + adminCommand: ['blacklist','server', 'status'], // Admin commands, only Admin role user can use djCommand: ['dj', 'filter'], // DJ commands, only DJ role user can use // Supported commands: 'skip', 'seek', 'pause' // When a command name is listed here, only the requester of the currently playing song may use it. diff --git a/package-lock.json b/package-lock.json index 26b7714..5411e30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,27 +10,27 @@ "license": "MIT", "dependencies": { "bcryptjs": "^3.0.3", - "better-sqlite3": "^12.10.0", + "better-sqlite3": "^12.10.1", "cookie": "^1.1.1", "discord.js": "~14.26.4", "dotenv": "^17.4.2", "express": "^5.2.1", - "i18next": "^26.2.0", + "i18next": "^26.3.1", "i18next-fs-backend": "^2.6.6", "lavashark": "^2.3.2", - "undici": "^8.3.0", + "undici": "^8.4.1", "zod": "^4.4.3" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/better-sqlite3": "^7.6.13", "@types/express": "^5.0.6", - "@types/node": "^25.9.1", - "eslint": "^10.4.0", + "@types/node": "^25.9.3", + "eslint": "^10.5.0", "globals": "^17.6.0", - "tsx": "^4.22.3", + "tsx": "^4.22.4", "typescript": "~6.0.3", - "typescript-eslint": "^8.59.4" + "typescript-eslint": "^8.61.0" }, "engines": { "node": ">=22.22.3" @@ -186,9 +186,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -203,9 +203,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -220,9 +220,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -237,9 +237,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -254,9 +254,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -271,9 +271,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -288,9 +288,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -305,9 +305,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -322,9 +322,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -339,9 +339,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -356,9 +356,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -373,9 +373,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -390,9 +390,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -407,9 +407,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -424,9 +424,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -441,9 +441,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -458,9 +458,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -475,9 +475,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -492,9 +492,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -509,9 +509,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -526,9 +526,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -543,9 +543,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -560,9 +560,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -577,9 +577,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -594,9 +594,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -611,9 +611,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -742,9 +742,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", - "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -951,9 +951,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -1004,17 +1004,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", - "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz", + "integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/type-utils": "8.60.0", - "@typescript-eslint/utils": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/type-utils": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1027,7 +1027,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.60.0", + "@typescript-eslint/parser": "^8.61.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -1043,16 +1043,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", - "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz", + "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3" }, "engines": { @@ -1068,14 +1068,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", - "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz", + "integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.60.0", - "@typescript-eslint/types": "^8.60.0", + "@typescript-eslint/tsconfig-utils": "^8.61.0", + "@typescript-eslint/types": "^8.61.0", "debug": "^4.4.3" }, "engines": { @@ -1090,14 +1090,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", - "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz", + "integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0" + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1108,9 +1108,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", - "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz", + "integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==", "dev": true, "license": "MIT", "engines": { @@ -1125,15 +1125,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", - "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz", + "integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1150,9 +1150,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", - "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz", + "integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==", "dev": true, "license": "MIT", "engines": { @@ -1164,16 +1164,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", - "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz", + "integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.60.0", - "@typescript-eslint/tsconfig-utils": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/project-service": "8.61.0", + "@typescript-eslint/tsconfig-utils": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1192,16 +1192,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", - "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz", + "integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0" + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1216,13 +1216,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", - "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz", + "integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1336,9 +1336,9 @@ } }, "node_modules/better-sqlite3": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz", - "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", + "version": "12.10.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.1.tgz", + "integrity": "sha512-HfFtzCqnSfwB3+HroF6PSKzyh+7RfNMGPCzHFUZXRlvrPCb4P3cvxKZNN43Sr7IrkofqQZM+gIvffGpA8VvqgA==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1725,9 +1725,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1738,32 +1738,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escape-html": { @@ -1786,18 +1786,21 @@ } }, "node_modules/eslint": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz", - "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2297,9 +2300,9 @@ } }, "node_modules/i18next": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.0.tgz", - "integrity": "sha512-gHSgGpUXVmuqE2El1W61DmxeyeTlFfZgdJRWMo9jScAn5pu7TuTuiccb1zh3E2J9hEBVGJ23+96x0ieBhfuIHA==", + "version": "26.3.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.1.tgz", + "integrity": "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ==", "funding": [ { "type": "individual", @@ -3269,9 +3272,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3320,9 +3323,9 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", - "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { @@ -3409,16 +3412,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", - "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.0.tgz", + "integrity": "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.60.0", - "@typescript-eslint/parser": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0" + "@typescript-eslint/eslint-plugin": "8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3433,9 +3436,9 @@ } }, "node_modules/undici": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.3.0.tgz", - "integrity": "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.4.1.tgz", + "integrity": "sha512-RNHlB4fxZK0IrkhBsxhlbx7s8kFWwr7rzzOqj5nvZugw3ig3RsB7KW3zVlV0eu8POl+rx5d1hmL7rRg0z1owow==", "license": "MIT", "engines": { "node": ">=22.19.0" diff --git a/package.json b/package.json index 417ca68..7df428b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "build:dashboard": "cd dashboard && npm run generate", "install:dashboard": "cd dashboard && npm install", "dev": "tsc --noEmit --incremental false && tsx ./src/index.ts", - "lint": "eslint . --color" + "lint": "eslint . --color", + "typecheck": "npx tsc --noEmit" }, "repository": { "type": "git", @@ -25,33 +26,31 @@ }, "homepage": "https://github.com/hmes98318/Music-Disc#readme", "overrides": { - "brace-expansion": "^5.0.6", - "qs": "^6.15.2", - "ws": "^8.21.0" + "esbuild": "^0.28.1" }, "dependencies": { "bcryptjs": "^3.0.3", - "better-sqlite3": "^12.10.0", + "better-sqlite3": "^12.10.1", "cookie": "^1.1.1", "discord.js": "~14.26.4", "dotenv": "^17.4.2", "express": "^5.2.1", - "i18next": "^26.2.0", + "i18next": "^26.3.1", "i18next-fs-backend": "^2.6.6", "lavashark": "^2.3.2", - "undici": "^8.3.0", + "undici": "^8.4.1", "zod": "^4.4.3" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/better-sqlite3": "^7.6.13", "@types/express": "^5.0.6", - "@types/node": "^25.9.1", - "eslint": "^10.4.0", + "@types/node": "^25.9.3", + "eslint": "^10.5.0", "globals": "^17.6.0", - "tsx": "^4.22.3", + "tsx": "^4.22.4", "typescript": "~6.0.3", - "typescript-eslint": "^8.59.4" + "typescript-eslint": "^8.61.0" }, "engines": { "node": ">=22.22.3" diff --git a/src/@types/index.ts b/src/@types/index.ts index 39fab5e..c09890a 100644 --- a/src/@types/index.ts +++ b/src/@types/index.ts @@ -11,6 +11,7 @@ import type { NodeOptions } from 'lavashark/typings/src/@types/index.js'; import type { Language } from '../lib/i18n/Language.js'; import type { Logger } from '../lib/Logger.js'; import type { BlacklistManager } from '../lib/BlacklistManager.js'; +import type { GuildLanguageManager } from '../lib/GuildLanguageManager.js'; import type { DashboardManager } from '../lib/DashboardManager.js'; import type { CommandRegistry } from '../commands/base/CommandRegistry.js'; import type { IPBlockerConfig, SessionManagerConfig } from './SessionManager.types.js'; @@ -87,6 +88,7 @@ export type Bot = { i18n: i18n; lang: Language; blacklistManager?: BlacklistManager; + guildLanguageManager?: GuildLanguageManager; } /** diff --git a/src/App.ts b/src/App.ts index d61e175..6f5b5ef 100644 --- a/src/App.ts +++ b/src/App.ts @@ -11,6 +11,7 @@ import { } from './loader/index.js'; import { Logger } from './lib/Logger.js'; import { BlacklistManager } from './lib/BlacklistManager.js'; +import { GuildLanguageManager } from './lib/GuildLanguageManager.js'; import { DashboardManager } from './lib/DashboardManager.js'; import { QueuePersistence } from './lib/QueuePersistence.js'; import { cst } from './utils/constants.js'; @@ -91,6 +92,10 @@ class App { (this.#client as any).queuePersistence = new QueuePersistence(this.bot); (this.#client as any).queuePersistence.initialize(); } + + // Initialize guild language manager + this.bot.guildLanguageManager = new GuildLanguageManager(this.bot); + this.bot.guildLanguageManager.initialize(); } @@ -166,6 +171,12 @@ class App { this.bot.blacklistManager.close(); } + // Close guild language manager database + if (this.bot.guildLanguageManager) { + this.bot.logger.log( this.bot.shardId, 'Closing guild language database...'); + this.bot.guildLanguageManager.close(); + } + clearTimeout(timeout); this.bot.logger.log( this.bot.shardId, 'Server closed gracefully.'); diff --git a/src/commands/BlacklistCommand.ts b/src/commands/BlacklistCommand.ts index 0d78816..beae620 100644 --- a/src/commands/BlacklistCommand.ts +++ b/src/commands/BlacklistCommand.ts @@ -59,7 +59,7 @@ export class BlacklistCommand extends BaseCommand { protected async run(bot: Bot, client: Client, context: CommandContext): Promise { if (!bot.blacklistManager) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_BLACKLIST_NOT_INITIALIZED')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_BLACKLIST_NOT_INITIALIZED')); return; } @@ -99,7 +99,7 @@ export class BlacklistCommand extends BaseCommand { const action = args[0]?.toLowerCase(); if (!action || !['add', 'remove', 'list'].includes(action)) { - await context.replyEphemeralError(bot, client.i18n.t('commands:CONFIG_BLACKLIST_USAGE')); + await context.replyEphemeralError(bot, context.t('commands:CONFIG_BLACKLIST_USAGE')); return; } @@ -111,7 +111,7 @@ export class BlacklistCommand extends BaseCommand { // Extract user ID from mention or raw ID const userArg = args[1]; if (!userArg) { - await context.replyEphemeralError(bot, client.i18n.t('commands:CONFIG_BLACKLIST_USAGE')); + await context.replyEphemeralError(bot, context.t('commands:CONFIG_BLACKLIST_USAGE')); return; } @@ -127,18 +127,18 @@ export class BlacklistCommand extends BaseCommand { async #addUser(bot: Bot, _client: Client, context: CommandContext, userId: string): Promise { const success = bot.blacklistManager!.add(userId); if (success) { - await context.replySuccess(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_ADDED', { userId })); + await context.replySuccess(bot, context.t('commands:MESSAGE_BLACKLIST_ADDED', { userId })); } else { - await context.replyEphemeralError(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_ALREADY_LISTED', { userId })); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_BLACKLIST_ALREADY_LISTED', { userId })); } } async #removeUser(bot: Bot, _client: Client, context: CommandContext, userId: string): Promise { const success = bot.blacklistManager!.remove(userId); if (success) { - await context.replySuccess(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_REMOVED', { userId })); + await context.replySuccess(bot, context.t('commands:MESSAGE_BLACKLIST_REMOVED', { userId })); } else { - await context.replyEphemeralError(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_NOT_LISTED', { userId })); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_BLACKLIST_NOT_LISTED', { userId })); } } @@ -146,7 +146,7 @@ export class BlacklistCommand extends BaseCommand { const users = bot.blacklistManager!.getAll(); if (users.length === 0) { - await context.replyText(bot, _client.i18n.t('commands:MESSAGE_BLACKLIST_LIST_EMPTY')); + await context.replyText(bot, context.t('commands:MESSAGE_BLACKLIST_LIST_EMPTY')); return; } @@ -155,7 +155,7 @@ export class BlacklistCommand extends BaseCommand { const embed = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as any) - .setTitle(_client.i18n.t('commands:MESSAGE_BLACKLIST_LIST_TITLE')) + .setTitle(context.t('commands:MESSAGE_BLACKLIST_LIST_TITLE')) .setDescription(userList) .setTimestamp(); diff --git a/src/commands/ClearCommand.ts b/src/commands/ClearCommand.ts index 3df8381..86e606c 100644 --- a/src/commands/ClearCommand.ts +++ b/src/commands/ClearCommand.ts @@ -26,7 +26,7 @@ export class ClearCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -41,7 +41,7 @@ export class ClearCommand extends BaseCommand { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_CLEAR_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_CLEAR_SUCCESS')); } } } diff --git a/src/commands/DashboardCommand.ts b/src/commands/DashboardCommand.ts index 88f6b4a..7ac4cec 100644 --- a/src/commands/DashboardCommand.ts +++ b/src/commands/DashboardCommand.ts @@ -26,7 +26,7 @@ export class DashboardCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.dashboardMsg) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -54,7 +54,7 @@ export class DashboardCommand extends BaseCommand { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_DASHBOARD_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_DASHBOARD_SUCCESS')); } } } diff --git a/src/commands/DjCommand.ts b/src/commands/DjCommand.ts index 25f68b1..ca9d33b 100644 --- a/src/commands/DjCommand.ts +++ b/src/commands/DjCommand.ts @@ -53,31 +53,31 @@ export class DjCommand extends BaseCommand { // Check permission for adding DJ - only admins can add/remove DJs if (!bot.config.bot.admin.includes(context.user.id)) { - await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_ADMIN_ONLY')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_ADMIN_ONLY')); return; } // Validate user if (targetUser.bot) { - await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_NO_BOTS')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_NO_BOTS')); return; } // Handle different DJ modes if (bot.config.bot.djMode === DJModeEnum.STATIC) { - await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_STATIC_MODE')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_STATIC_MODE')); return; } // DYNAMIC mode - add to current player's DJ list else { if (!player) { - await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_NO_PLAYER')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_NO_PLAYER')); return; } // Check if already DJ if (DJManager.isDJ(bot, targetUser.id, null, player)) { - await context.replyEphemeralError(bot, i18next.t('commands:MESSAGE_DJ_ALREADY_DJ', { + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_DJ_ALREADY_DJ', { userId: targetUser.id })); return; @@ -85,7 +85,7 @@ export class DjCommand extends BaseCommand { // Add DJ DJManager.addDJ(player, targetUser.id); - await context.replySuccess(bot, i18next.t('commands:MESSAGE_DJ_SUCCESS', { + await context.replySuccess(bot, context.t('commands:MESSAGE_DJ_SUCCESS', { userId: targetUser.id })); } @@ -95,23 +95,23 @@ export class DjCommand extends BaseCommand { try { const djInfo = await DJManager.getDJInfo(bot, client, context.guild!, player || undefined); - let description = i18next.t('commands:MESSAGE_DJ_LIST_TITLE') + '\n\n'; + let description = context.t('commands:MESSAGE_DJ_LIST_TITLE') + '\n\n'; // Add admins if (djInfo.admins.length > 0) { - description += `**${i18next.t('commands:MESSAGE_DJ_LIST_ADMINS')}**\n`; + description += `**${context.t('commands:MESSAGE_DJ_LIST_ADMINS')}**\n`; description += djInfo.admins.map(id => `<@${id}>`).join(', ') + '\n\n'; } // Add role-based DJs if (djInfo.roleDJs.length > 0) { - description += `**${i18next.t('commands:MESSAGE_DJ_LIST_ROLE_DJS')}**\n`; + description += `**${context.t('commands:MESSAGE_DJ_LIST_ROLE_DJS')}**\n`; description += djInfo.roleDJs.map(id => `<@${id}>`).join(', ') + '\n\n'; } // Add dynamic DJs if (djInfo.dynamicDJs.length > 0) { - description += `**${i18next.t('commands:MESSAGE_DJ_LIST_DYNAMIC_DJS')}**\n`; + description += `**${context.t('commands:MESSAGE_DJ_LIST_DYNAMIC_DJS')}**\n`; description += djInfo.dynamicDJs.map(id => `<@${id}>`).join(', ') + '\n\n'; } @@ -119,17 +119,17 @@ export class DjCommand extends BaseCommand { if (bot.config.bot.djRoleId) { description += `**DJ Role:** <@&${bot.config.bot.djRoleId}>\n`; } else { - description += i18next.t('commands:MESSAGE_DJ_ROLE_NOT_SET') + '\n'; + description += context.t('commands:MESSAGE_DJ_ROLE_NOT_SET') + '\n'; } if (djInfo.admins.length === 0 && djInfo.roleDJs.length === 0 && djInfo.dynamicDJs.length === 0) { - description = i18next.t('commands:MESSAGE_DJ_LIST_NONE'); + description = context.t('commands:MESSAGE_DJ_LIST_NONE'); } await context.replySuccess(bot, description); } catch (error) { bot.logger.error( bot.shardId, `Error showing DJ list: ${error}`); - await context.replyError(bot, i18next.t('commands:MESSAGE_DJ_LIST_ERROR')); + await context.replyError(bot, context.t('commands:MESSAGE_DJ_LIST_ERROR')); } } } diff --git a/src/commands/FilterCommand.ts b/src/commands/FilterCommand.ts index 07f3f68..b1af98f 100644 --- a/src/commands/FilterCommand.ts +++ b/src/commands/FilterCommand.ts @@ -50,7 +50,7 @@ export class FilterCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -81,18 +81,18 @@ export class FilterCommand extends BaseCommand { ): Promise { const select = new StringSelectMenuBuilder() .setCustomId(SelectButtonId.Filter) - .setPlaceholder(client.i18n.t('commands:MESSAGE_FILTER_SELECT_MODE')) + .setPlaceholder(context.t('commands:MESSAGE_FILTER_SELECT_MODE')) .setOptions([ ...(Object.keys(filtersConfig).map((effectName) => ({ label: effectName, value: effectName }))), - { label: client.i18n.t('commands:LABEL_FILTER_CLEAR'), value: 'clear' } + { label: context.t('commands:LABEL_FILTER_CLEAR'), value: 'clear' } ]); const row = new ActionRowBuilder().addComponents(select); const msg = await context.reply({ - embeds: [embeds.textMsg(bot, client.i18n.t('commands:MESSAGE_FILTER_SELECT_LIST'))], + embeds: [embeds.textMsg(bot, context.t('commands:MESSAGE_FILTER_SELECT_LIST'))], components: [row.toJSON()], allowedMentions: { repliedUser: false } }); @@ -113,7 +113,7 @@ export class FilterCommand extends BaseCommand { else { if (!Object.keys(filtersConfig).includes(effectName)) { await context.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:MESSAGE_FILTER_NOT_FOUND'))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:MESSAGE_FILTER_NOT_FOUND'))], allowedMentions: { repliedUser: false } }); collector.stop(); @@ -129,7 +129,7 @@ export class FilterCommand extends BaseCommand { await i.deferUpdate(); await msg.edit({ - embeds: [embeds.filterMsg(bot, effectName)], + embeds: [embeds.filterMsg(bot, effectName, context.language)], components: [], allowedMentions: { repliedUser: false } }).catch(() => @@ -142,7 +142,7 @@ export class FilterCommand extends BaseCommand { collector.on('end', async (collected: Collection, reason: string) => { if (reason === 'time' && collected.size === 0) { await msg.edit({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_TIME_EXPIRED'))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_TIME_EXPIRED'))], components: [], allowedMentions: { repliedUser: false } }).catch(() => @@ -168,7 +168,7 @@ export class FilterCommand extends BaseCommand { } else { if (!Object.keys(filtersConfig).includes(effectName)) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_FILTER_NOT_FOUND')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_FILTER_NOT_FOUND')); return; } @@ -180,7 +180,7 @@ export class FilterCommand extends BaseCommand { } await context.reply({ - embeds: [embeds.filterMsg(bot, effectName)], + embeds: [embeds.filterMsg(bot, effectName, context.language)], components: [], allowedMentions: { repliedUser: false } }).catch(() => diff --git a/src/commands/HelpCommand.ts b/src/commands/HelpCommand.ts index 339168d..e56929a 100644 --- a/src/commands/HelpCommand.ts +++ b/src/commands/HelpCommand.ts @@ -74,26 +74,26 @@ export class HelpCommand extends BaseCommand { // Build select menus const musicSelect = new StringSelectMenuBuilder() .setCustomId(SelectButtonId.HelpMusic) - .setPlaceholder(client.i18n.t('commands:HELP_SELECT_MUSIC_PLACEHOLDER')) + .setPlaceholder(context.t('commands:HELP_SELECT_MUSIC_PLACEHOLDER')) .setOptions(musicCommands.map(cmd => { const metadata = cmd.getMetadata(bot); - const aliases = metadata.aliases && metadata.aliases.length > 0 ? metadata.aliases.join(', ') : client.i18n.t('commands:HELP_COMMAND_NONE'); + const aliases = metadata.aliases && metadata.aliases.length > 0 ? metadata.aliases.join(', ') : context.t('commands:HELP_COMMAND_NONE'); return { label: metadata.name, - description: client.i18n.t('commands:HELP_COMMAND_ALIASES', { aliases }), + description: context.t('commands:HELP_COMMAND_ALIASES', { aliases }), value: metadata.name }; })); const utilitySelect = new StringSelectMenuBuilder() .setCustomId(SelectButtonId.HelpUtility) - .setPlaceholder(client.i18n.t('commands:HELP_SELECT_UTILITY_PLACEHOLDER')) + .setPlaceholder(context.t('commands:HELP_SELECT_UTILITY_PLACEHOLDER')) .setOptions(utilityCommands.map(cmd => { const metadata = cmd.getMetadata(bot); - const aliases = metadata.aliases && metadata.aliases.length > 0 ? metadata.aliases.join(', ') : client.i18n.t('commands:HELP_COMMAND_NONE'); + const aliases = metadata.aliases && metadata.aliases.length > 0 ? metadata.aliases.join(', ') : context.t('commands:HELP_COMMAND_NONE'); return { label: metadata.name, - description: client.i18n.t('commands:HELP_COMMAND_ALIASES', { aliases }), + description: context.t('commands:HELP_COMMAND_ALIASES', { aliases }), value: metadata.name }; })); @@ -103,7 +103,7 @@ export class HelpCommand extends BaseCommand { // Send message const msg = await context.reply({ - embeds: [embeds.textMsg(bot, client.i18n.t('commands:MESSAGE_HELP_SELECT_LIST'))], + embeds: [embeds.textMsg(bot, context.t('commands:MESSAGE_HELP_SELECT_LIST'))], components: [musicRow.toJSON(), utilityRow.toJSON()], allowedMentions: { repliedUser: false } }); @@ -125,7 +125,7 @@ export class HelpCommand extends BaseCommand { await i.deferUpdate(); await msg.edit({ - embeds: [embeds.help(bot, title!, usage)], + embeds: [embeds.help(bot, title!, usage, context.language)], components: [], allowedMentions: { repliedUser: false } }).catch(() => bot.logger.discord( bot.shardId, 'Failed to edit deleted message.')); @@ -136,7 +136,7 @@ export class HelpCommand extends BaseCommand { collector.on('end', async (collected: Collection, reason: string) => { if (reason === 'time' && collected.size === 0) { await msg.edit({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_TIME_EXPIRED'))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_TIME_EXPIRED'))], components: [], allowedMentions: { repliedUser: false } }).catch(() => bot.logger.discord( bot.shardId, 'Failed to edit deleted message.')); @@ -159,7 +159,7 @@ export class HelpCommand extends BaseCommand { const description = `${metadata.description}\n\`\`\`${prefix}${metadata.usage}\`\`\``; await context.reply({ - embeds: [embeds.help(bot, metadata.name, description)], + embeds: [embeds.help(bot, metadata.name, description, context.language)], allowedMentions: { repliedUser: false } }); @@ -170,7 +170,7 @@ export class HelpCommand extends BaseCommand { if (!found) { await context.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:MESSAGE_HELP_NOT_FOUND'))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:MESSAGE_HELP_NOT_FOUND'))], allowedMentions: { repliedUser: false } }); } diff --git a/src/commands/LanguageCommand.ts b/src/commands/LanguageCommand.ts index 340208d..908511f 100644 --- a/src/commands/LanguageCommand.ts +++ b/src/commands/LanguageCommand.ts @@ -37,7 +37,7 @@ export class LanguageCommand extends BaseCommand { // Show available languages if (!locale) { - await context.replyText(bot, client.i18n.t('commands:MESSAGE_LANG_AVAILABLE_LIST', { + await context.replyText(bot, context.t('commands:MESSAGE_LANG_AVAILABLE_LIST', { langList: bot.lang.languages.map(lang => `\`${lang}\``).join(', ') })); return; @@ -45,20 +45,22 @@ export class LanguageCommand extends BaseCommand { // Validate language if (!bot.lang.languages.includes(locale)) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_LANG_ARGS_ERROR', { + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_LANG_ARGS_ERROR', { langList: bot.lang.languages.map(lang => `\`${lang}\``).join(', ') })); return; } - // Change language - await client.i18n.changeLanguage(locale); + // Change language for the current guild + if (context.guildId) { + bot.guildLanguageManager?.set(context.guildId, locale); + } if (context.isMessage()) { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_LANG_SUCCESS', { locale })); + await context.replySuccess(bot, context.t('commands:MESSAGE_LANG_SUCCESS', { locale })); } } } diff --git a/src/commands/LeaveCommand.ts b/src/commands/LeaveCommand.ts index b0218ef..d3210af 100644 --- a/src/commands/LeaveCommand.ts +++ b/src/commands/LeaveCommand.ts @@ -27,7 +27,7 @@ export class LeaveCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -42,7 +42,7 @@ export class LeaveCommand extends BaseCommand { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_LEAVE_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_LEAVE_SUCCESS')); } } } diff --git a/src/commands/LoopCommand.ts b/src/commands/LoopCommand.ts index 6be664c..54887ad 100644 --- a/src/commands/LoopCommand.ts +++ b/src/commands/LoopCommand.ts @@ -50,7 +50,7 @@ export class LoopCommand extends BaseCommand { const metadata = this.getMetadata(bot); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -60,7 +60,7 @@ export class LoopCommand extends BaseCommand { : context.args.join(' '); if (!modeParam) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_LOOP_COMMAND', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_LOOP_COMMAND', { command: `${bot.config.bot.prefix}${metadata.usage}` })); return; @@ -68,9 +68,9 @@ export class LoopCommand extends BaseCommand { let mode: number; const methods = [ - client.i18n.t('commands:REPEAT_MODE_OFF'), - client.i18n.t('commands:REPEAT_MODE_SINGLE'), - client.i18n.t('commands:REPEAT_MODE_ALL') + context.t('commands:REPEAT_MODE_OFF'), + context.t('commands:REPEAT_MODE_SINGLE'), + context.t('commands:REPEAT_MODE_ALL') ]; switch (modeParam.toLowerCase()) { @@ -92,7 +92,7 @@ export class LoopCommand extends BaseCommand { break; } default: { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_LOOP_COMMAND', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_LOOP_COMMAND', { command: `${bot.config.bot.prefix}${metadata.usage}` })); return; @@ -103,7 +103,7 @@ export class LoopCommand extends BaseCommand { await context.react('šŸ‘'); } - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_LOOP_MODE', { + await context.replySuccess(bot, context.t('commands:MESSAGE_LOOP_MODE', { mode: methods[mode] })); } diff --git a/src/commands/MoveCommand.ts b/src/commands/MoveCommand.ts index 63abced..1f46973 100644 --- a/src/commands/MoveCommand.ts +++ b/src/commands/MoveCommand.ts @@ -42,12 +42,12 @@ export class MoveCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } if (!player.queue.size) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_MUSIC_IN_QUEUE')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_MUSIC_IN_QUEUE')); return; } @@ -62,7 +62,7 @@ export class MoveCommand extends BaseCommand { index2 = parseInt(context.args[1], 10); if (isNaN(index1) || isNaN(index2)) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_MOVE_WRONG_INDEX', { + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_MOVE_WRONG_INDEX', { max: player.queue.size })); return; @@ -76,7 +76,7 @@ export class MoveCommand extends BaseCommand { } if (!isSuccess) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_MOVE_WRONG_INDEX', { + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_MOVE_WRONG_INDEX', { max: player.queue.size })); return; @@ -86,7 +86,7 @@ export class MoveCommand extends BaseCommand { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_MOVE_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_MOVE_SUCCESS')); } } } diff --git a/src/commands/NodeStatusCommand.ts b/src/commands/NodeStatusCommand.ts index bfccd85..397272f 100644 --- a/src/commands/NodeStatusCommand.ts +++ b/src/commands/NodeStatusCommand.ts @@ -63,17 +63,17 @@ export class NodeStatusCommand extends BaseCommand { if (ping === -1) { unhealthValue++; - nodesStatus.push({ name: `āŒ ${node.identifier}`, value: `**${bot.i18n.t('embeds:NODE_DISCONNECTED')}**` }); + nodesStatus.push({ name: `āŒ ${node.identifier}`, value: `**${context.t('embeds:NODE_DISCONNECTED')}**` }); } else { - nodesStatus.push({ name: `āœ… ${node.identifier}`, value: `${bot.i18n.t('embeds:NODE_STATUS_PING')}: **${ping}ms**` }); + nodesStatus.push({ name: `āœ… ${node.identifier}`, value: `${context.t('embeds:NODE_STATUS_PING')}: **${ping}ms**` }); } } bot.logger.log( bot.shardId, 'nodesStatus: ' + JSON.stringify(nodesStatus)); await context.reply({ - embeds: [embeds.nodesStatus(bot, unhealthValue, nodesStatus)], + embeds: [embeds.nodesStatus(bot, unhealthValue, nodesStatus, context.language)], allowedMentions: { repliedUser: false } }); } @@ -89,7 +89,7 @@ export class NodeStatusCommand extends BaseCommand { if (node.identifier === nodeName) { if (node.state !== NodeState.CONNECTED) { await context.reply({ - embeds: [embeds.nodeDisconnected(bot, nodeName)], + embeds: [embeds.nodeDisconnected(bot, nodeName, context.language)], allowedMentions: { repliedUser: false } }); return; @@ -109,7 +109,7 @@ export class NodeStatusCommand extends BaseCommand { bot.logger.log( bot.shardId, 'nodePing: ' + nodePing + 'ms'); await context.reply({ - embeds: [embeds.nodeStatus(bot, nodeName, nodeInfo, nodeStats, nodePing)], + embeds: [embeds.nodeStatus(bot, nodeName, nodeInfo, nodeStats, nodePing, context.language)], allowedMentions: { repliedUser: false } }); return; @@ -123,7 +123,7 @@ export class NodeStatusCommand extends BaseCommand { } await context.reply({ - embeds: [embeds.validNodeName(bot, nodesName)], + embeds: [embeds.validNodeName(bot, nodesName, context.language)], allowedMentions: { repliedUser: false } }); } diff --git a/src/commands/NowPlayingCommand.ts b/src/commands/NowPlayingCommand.ts index d11f507..66d6336 100644 --- a/src/commands/NowPlayingCommand.ts +++ b/src/commands/NowPlayingCommand.ts @@ -33,21 +33,21 @@ export class NowPlayingCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } const track = player.current; const requester = track?.requester; const requesterInfo = requester?.id ? ` | <@${requester.id}>` : ''; - const subtitle = client.i18n.t('commands:MESSAGE_NOW_PLAYING_SUBTITLE', { + const subtitle = context.t('commands:MESSAGE_NOW_PLAYING_SUBTITLE', { author: track?.author, label: track?.duration.label }) + requesterInfo; const saveButton = new ButtonBuilder() .setCustomId(MusicButtonId.Save) - .setLabel(client.i18n.t('commands:MESSAGE_NOW_PLAYING_SAVE_BUTTON')) + .setLabel(context.t('commands:MESSAGE_NOW_PLAYING_SAVE_BUTTON')) .setStyle(ButtonStyle.Success); const row = new ActionRowBuilder() .addComponents(saveButton); diff --git a/src/commands/PauseCommand.ts b/src/commands/PauseCommand.ts index 5af24a9..5f1546d 100644 --- a/src/commands/PauseCommand.ts +++ b/src/commands/PauseCommand.ts @@ -31,12 +31,12 @@ export class PauseCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guildId!); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } if (player.paused) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_PAUSE_MUSIC_PAUSED')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_PAUSE_MUSIC_PAUSED')); return; } @@ -52,7 +52,7 @@ export class PauseCommand extends BaseCommand { const isDJ = PermissionManager.hasDJCommandPermission(bot, userId, member, player); const canDJBypass = bot.config.command.requesterDjBypass.includes('pause') && isDJ; if (!isRequester && !isAdmin && !canDJBypass) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PAUSE_NOT_REQUESTER')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_PAUSE_NOT_REQUESTER')); return; } } @@ -64,10 +64,10 @@ export class PauseCommand extends BaseCommand { } else { if (success) { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_PAUSE_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_PAUSE_SUCCESS')); } else { - await context.replyError(bot, client.i18n.t('commands:MESSAGE_PAUSE_FAIL')); + await context.replyError(bot, context.t('commands:MESSAGE_PAUSE_FAIL')); } } } diff --git a/src/commands/PingCommand.ts b/src/commands/PingCommand.ts index fa9c2d5..2ec80b7 100644 --- a/src/commands/PingCommand.ts +++ b/src/commands/PingCommand.ts @@ -34,7 +34,7 @@ export class PingCommand extends BaseCommand { await context.react('šŸ‘'); await context.reply({ - embeds: [embeds.ping(bot, botPing, apiPing)] + embeds: [embeds.ping(bot, botPing, apiPing, context.language)] }); } } diff --git a/src/commands/PlayCommand.ts b/src/commands/PlayCommand.ts index 10f4b1b..21a0d80 100644 --- a/src/commands/PlayCommand.ts +++ b/src/commands/PlayCommand.ts @@ -42,7 +42,7 @@ export class PlayCommand extends BaseCommand { : context.getStringOption('play'); if (!str) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_PLAY_ARGS_ERROR')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_PLAY_ARGS_ERROR')); return; } @@ -53,7 +53,7 @@ export class PlayCommand extends BaseCommand { } catch (error) { console.error(error); bot.logger.error( bot.shardId, `Search Error: ${error}`); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_SEARCH', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_SEARCH', { reason: error instanceof Error ? error.message : String(error) })); return; @@ -62,13 +62,13 @@ export class PlayCommand extends BaseCommand { // Handle search results if (res.loadType === LoadType.ERROR) { bot.logger.error( bot.shardId, `Search Error: ${JSON.stringify(res)}`); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_SEARCH', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_SEARCH', { reason: (res as any).data?.message })); return; } else if (res.loadType === LoadType.EMPTY) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_PLAY_SEARCH_NO_MATCH')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_PLAY_SEARCH_NO_MATCH')); return; } @@ -80,7 +80,7 @@ export class PlayCommand extends BaseCommand { const validBlackist = isUserInBlacklist(voiceChannel, bot.config.blacklist, bot.blacklistManager); if (validBlackist.length > 0) { await context.reply({ - embeds: [embeds.blacklist(bot, validBlackist)] + embeds: [embeds.blacklist(bot, validBlackist, context.language)] }); return; } @@ -105,7 +105,7 @@ export class PlayCommand extends BaseCommand { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_PLAY_MUSIC_ADD')); + await context.replySuccess(bot, context.t('commands:MESSAGE_PLAY_MUSIC_ADD')); } } @@ -140,7 +140,7 @@ export class PlayCommand extends BaseCommand { player.metadata = metadata; } catch (error) { bot.logger.error( bot.shardId, 'Error joining channel: ' + error); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_JOIN_CHANNEL')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_JOIN_CHANNEL')); return null; } @@ -189,7 +189,7 @@ export class PlayCommand extends BaseCommand { const checkResult = QueueLimitManager.canAddSongs(bot, player, userId, guildMember, 1); if (!checkResult.canAdd) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_QUEUE_LIMIT_REACHED', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_QUEUE_LIMIT_REACHED', { current: checkResult.currentCount, limit: checkResult.limit })); @@ -204,7 +204,7 @@ export class PlayCommand extends BaseCommand { const playlistCheck = QueueLimitManager.calculatePlaylistAddition(bot, player, userId, guildMember, playlistSize); if (playlistCheck.limitReached) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_QUEUE_LIMIT_REACHED', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_QUEUE_LIMIT_REACHED', { current: QueueLimitManager.countUserSongsInQueue(player, userId), limit: QueueLimitManager.getUserLimit(bot, userId, guildMember, player) })); @@ -216,7 +216,7 @@ export class PlayCommand extends BaseCommand { const currentCount = QueueLimitManager.countUserSongsInQueue(player, userId); const limit = QueueLimitManager.getUserLimit(bot, userId, guildMember, player); - await context.replyWarning(bot, client.i18n.t('commands:MESSAGE_PLAYLIST_PARTIAL', { + await context.replyWarning(bot, context.t('commands:MESSAGE_PLAYLIST_PARTIAL', { added: playlistCheck.canAddCount, skipped: playlistCheck.willSkipCount, current: currentCount + playlistCheck.canAddCount, @@ -252,7 +252,7 @@ export class PlayCommand extends BaseCommand { await player.play() .catch(async (error) => { bot.logger.error( bot.shardId, 'Error playing track: ' + error); - await context.replyError(bot, client.i18n.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); + await context.replyError(bot, context.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); return player.destroy(); }); } diff --git a/src/commands/PlayFirstCommand.ts b/src/commands/PlayFirstCommand.ts index a3bd26c..3fca264 100644 --- a/src/commands/PlayFirstCommand.ts +++ b/src/commands/PlayFirstCommand.ts @@ -42,7 +42,7 @@ export class PlayFirstCommand extends BaseCommand { : context.getStringOption('playfirst'); if (!str) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_PLAY_ARGS_ERROR')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_PLAY_ARGS_ERROR')); return; } @@ -53,7 +53,7 @@ export class PlayFirstCommand extends BaseCommand { } catch (error) { console.error(error); bot.logger.error( bot.shardId, `Search Error: ${error}`); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_SEARCH', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_SEARCH', { reason: error instanceof Error ? error.message : String(error) })); return; @@ -62,13 +62,13 @@ export class PlayFirstCommand extends BaseCommand { // Handle search results if (res.loadType === LoadType.ERROR) { bot.logger.error( bot.shardId, `Search Error: ${JSON.stringify(res)}`); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_SEARCH', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_SEARCH', { reason: (res as any).data?.message })); return; } else if (res.loadType === LoadType.EMPTY) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_PLAY_SEARCH_NO_MATCH')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_PLAY_SEARCH_NO_MATCH')); return; } @@ -80,7 +80,7 @@ export class PlayFirstCommand extends BaseCommand { const validBlackist = isUserInBlacklist(voiceChannel, bot.config.blacklist, bot.blacklistManager); if (validBlackist.length > 0) { await context.reply({ - embeds: [embeds.blacklist(bot, validBlackist)] + embeds: [embeds.blacklist(bot, validBlackist, context.language)] }); return; } @@ -104,7 +104,7 @@ export class PlayFirstCommand extends BaseCommand { if (context.isMessage()) { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_PLAY_MUSIC_ADD')); + await context.replySuccess(bot, context.t('commands:MESSAGE_PLAY_MUSIC_ADD')); } } @@ -140,7 +140,7 @@ export class PlayFirstCommand extends BaseCommand { player.metadata = metadata; } catch (error) { bot.logger.error( bot.shardId, 'Error joining channel: ' + error); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_JOIN_CHANNEL')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_JOIN_CHANNEL')); return null; } @@ -189,7 +189,7 @@ export class PlayFirstCommand extends BaseCommand { const checkResult = QueueLimitManager.canAddSongs(bot, player, userId, guildMember, 1); if (!checkResult.canAdd) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_QUEUE_LIMIT_REACHED', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_QUEUE_LIMIT_REACHED', { current: checkResult.currentCount, limit: checkResult.limit })); @@ -204,7 +204,7 @@ export class PlayFirstCommand extends BaseCommand { const playlistCheck = QueueLimitManager.calculatePlaylistAddition(bot, player, userId, guildMember, playlistSize); if (playlistCheck.limitReached) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_QUEUE_LIMIT_REACHED', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_QUEUE_LIMIT_REACHED', { current: QueueLimitManager.countUserSongsInQueue(player, userId), limit: QueueLimitManager.getUserLimit(bot, userId, guildMember, player) })); @@ -216,7 +216,7 @@ export class PlayFirstCommand extends BaseCommand { const currentCount = QueueLimitManager.countUserSongsInQueue(player, userId); const limit = QueueLimitManager.getUserLimit(bot, userId, guildMember, player); - await context.replyWarning(bot, client.i18n.t('commands:MESSAGE_PLAYLIST_PARTIAL', { + await context.replyWarning(bot, context.t('commands:MESSAGE_PLAYLIST_PARTIAL', { added: playlistCheck.canAddCount, skipped: playlistCheck.willSkipCount, current: currentCount + playlistCheck.canAddCount, @@ -247,7 +247,7 @@ export class PlayFirstCommand extends BaseCommand { await player.play() .catch(async (error) => { bot.logger.error( bot.shardId, 'Error playing track: ' + error); - await context.replyError(bot, client.i18n.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); + await context.replyError(bot, context.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); return player.destroy(); }); } @@ -257,7 +257,7 @@ export class PlayFirstCommand extends BaseCommand { await player.prioritizePlay(track, requester as any) .catch(async (error) => { bot.logger.error( bot.shardId, 'Error playing track: ' + error); - await context.replyError(bot, client.i18n.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); + await context.replyError(bot, context.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); return player.destroy(); }); } diff --git a/src/commands/PlayLastCommand.ts b/src/commands/PlayLastCommand.ts index 5ddd97b..6e79142 100644 --- a/src/commands/PlayLastCommand.ts +++ b/src/commands/PlayLastCommand.ts @@ -40,7 +40,7 @@ export class PlayLastCommand extends BaseCommand { const voiceChannel = member?.voice.channel; if (!voiceChannel) { - await context.replyEphemeralError(bot, client.i18n.t('events:ERROR_NOT_IN_VOICE_CHANNEL')); + await context.replyEphemeralError(bot, context.t('events:ERROR_NOT_IN_VOICE_CHANNEL')); return; } @@ -48,7 +48,7 @@ export class PlayLastCommand extends BaseCommand { // If player exists and queue is NOT empty: deny if (player && player.queue.tracks.length > 0) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAYLAST_QUEUE_NOT_EMPTY')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAYLAST_QUEUE_NOT_EMPTY')); return; } @@ -63,7 +63,7 @@ export class PlayLastCommand extends BaseCommand { // No player or not playing: check for last played track const lastTrack = client.lastPlayedTracks.get(guildId); if (!lastTrack) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAYLAST_NO_LAST_TRACK')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAYLAST_NO_LAST_TRACK')); return; } @@ -90,7 +90,7 @@ export class PlayLastCommand extends BaseCommand { newPlayer.metadata = metadata; } catch (error) { bot.logger.error( bot.shardId, 'Error joining channel: ' + error); - await context.replyError(bot, client.i18n.t('commands:ERROR_PLAY_JOIN_CHANNEL')); + await context.replyError(bot, context.t('commands:ERROR_PLAY_JOIN_CHANNEL')); return; } @@ -121,7 +121,7 @@ export class PlayLastCommand extends BaseCommand { await newPlayer.play() .catch(async (error) => { bot.logger.error( bot.shardId, 'Error playing track: ' + error); - await context.replyError(bot, client.i18n.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); + await context.replyError(bot, context.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); return newPlayer.destroy(); }); @@ -133,13 +133,13 @@ export class PlayLastCommand extends BaseCommand { * @private */ async #replyWithTrackEmbed(bot: Bot, client: Client, context: CommandContext, track: Track): Promise { - const subtitle = client.i18n.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { + const subtitle = context.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { author: track.author, label: track.duration.label }); await context.reply({ - embeds: [embeds.addTrack(bot, track.title, subtitle, track.uri, track.thumbnail!)] + embeds: [embeds.addTrack(bot, track.title, subtitle, track.uri, track.thumbnail!, context.language)] }); } } diff --git a/src/commands/QueueCommand.ts b/src/commands/QueueCommand.ts index c6bfedd..a199e1a 100644 --- a/src/commands/QueueCommand.ts +++ b/src/commands/QueueCommand.ts @@ -29,7 +29,7 @@ export class QueueCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -52,18 +52,18 @@ export class QueueCommand extends BaseCommand { const endIdx = page * 5; const queueTracks = player.queue.tracks.slice(startIdx, endIdx); - const description = this.#buildQueueDescription(client, player, queueTracks, startIdx, page, player.setting.queuePage.maxPage, player.queue.tracks.length); + const description = this.#buildQueueDescription(context, player, queueTracks, startIdx, page, player.setting.queuePage.maxPage, player.queue.tracks.length); const methods = [ - client.i18n.t('commands:REPEAT_MODE_OFF'), - client.i18n.t('commands:REPEAT_MODE_SINGLE'), - client.i18n.t('commands:REPEAT_MODE_ALL') + context.t('commands:REPEAT_MODE_OFF'), + context.t('commands:REPEAT_MODE_SINGLE'), + context.t('commands:REPEAT_MODE_ALL') ]; const repeatMode = player.repeatMode; const row = ButtonsBuilder.createQueueButtons(); player.setting.queuePage.msg = await context.reply({ - embeds: [embeds.queue(bot, description, methods[repeatMode])], + embeds: [embeds.queue(bot, description, methods[repeatMode], context.language)], components: [row], allowedMentions: { repliedUser: false }, }); @@ -78,7 +78,7 @@ export class QueueCommand extends BaseCommand { * @private */ #buildQueueDescription( - client: Client, + context: CommandContext, player: any, queueTracks: any[], startIdx: number, @@ -89,31 +89,31 @@ export class QueueCommand extends BaseCommand { let maxTitleLength = 80; const buildDescription = (titleLength: number): string => { - const nowPlayingTitle = player.current?.title || client.i18n.t('commands:UNKNOWN_USER'); + const nowPlayingTitle = player.current?.title || context.t('commands:UNKNOWN_USER'); const truncatedNP = nowPlayingTitle.length > titleLength ? nowPlayingTitle.substring(0, titleLength) + '...' : nowPlayingTitle; - let desc = `${client.i18n.t('embeds:QUEUE_NOW_PLAYING')}\n${truncatedNP}\n${'─'.repeat(20)}\n`; + let desc = `${context.t('embeds:QUEUE_NOW_PLAYING')}\n${truncatedNP}\n${'─'.repeat(20)}\n`; if (queueTracks.length < 1) { - desc += `\n*${client.i18n.t('embeds:QUEUE_EMPTY')}*`; + desc += `\n*${context.t('embeds:QUEUE_EMPTY')}*`; } else { - desc += `\n${client.i18n.t('embeds:QUEUE_HEADER')}\n`; + desc += `\n${context.t('embeds:QUEUE_HEADER')}\n`; const entries = queueTracks.map((track, index) => { let title = track.title; if (title.length > titleLength) { title = title.substring(0, titleLength) + '...'; } const requesterId = track.requester?.id; - const requesterMention = requesterId ? `<@${requesterId}>` : (track.requester?.username || client.i18n.t('commands:UNKNOWN_USER')); + const requesterMention = requesterId ? `<@${requesterId}>` : (track.requester?.username || context.t('commands:UNKNOWN_USER')); return `${startIdx + index + 1}. ${title} **${track.duration.label}** | ${requesterMention}`; }); desc += entries.join('\n'); } if (totalTracks > 0 && maxPage > 1) { - desc += `\n\n${client.i18n.t('events:MESSAGE_QUEUE_PAGE', { curPage, maxPage })}`; + desc += `\n\n${context.t('events:MESSAGE_QUEUE_PAGE', { curPage, maxPage })}`; } return desc; diff --git a/src/commands/RemoveCommand.ts b/src/commands/RemoveCommand.ts index 5667cf4..cb3edb7 100644 --- a/src/commands/RemoveCommand.ts +++ b/src/commands/RemoveCommand.ts @@ -41,14 +41,14 @@ export class RemoveCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } const tracks = player.queue.tracks.map((track, index) => `${index + 1}. \`${track.title}\``); if (tracks.length < 1) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_REMOVE_QUEUE_EMPTY')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_REMOVE_QUEUE_EMPTY')); return; } @@ -97,7 +97,7 @@ export class RemoveCommand extends BaseCommand { await context.react('āŒ'); } else { - await context.replyError(bot, client.i18n.t('commands:MESSAGE_REMOVE_FAIL')); + await context.replyError(bot, context.t('commands:MESSAGE_REMOVE_FAIL')); } return; } @@ -107,7 +107,7 @@ export class RemoveCommand extends BaseCommand { } await context.reply({ - embeds: [embeds.removeTrack(bot, tracks[index - 1])], + embeds: [embeds.removeTrack(bot, tracks[index - 1], context.language)], allowedMentions: { repliedUser: false } }); } @@ -136,7 +136,7 @@ export class RemoveCommand extends BaseCommand { await context.react('āŒ'); } else { - await context.replyError(bot, client.i18n.t('commands:MESSAGE_REMOVE_FAIL')); + await context.replyError(bot, context.t('commands:MESSAGE_REMOVE_FAIL')); } return; } @@ -148,7 +148,7 @@ export class RemoveCommand extends BaseCommand { } await context.reply({ - embeds: [embeds.removeTrack(bot, musicTitle)], + embeds: [embeds.removeTrack(bot, musicTitle, context.language)], allowedMentions: { repliedUser: false } }); } @@ -164,7 +164,7 @@ export class RemoveCommand extends BaseCommand { player: any, tracks: string[] ): Promise { - const nowplaying = client.i18n.t('commands:MESSAGE_NOW_PLAYING_TITLE', { + const nowplaying = context.t('commands:MESSAGE_NOW_PLAYING_TITLE', { title: player.current?.title }); @@ -174,7 +174,7 @@ export class RemoveCommand extends BaseCommand { } else if (tracks.length > 9) { tracksQueue = tracks.slice(0, 10).join('\n'); - tracksQueue += client.i18n.t('commands:MESSAGE_NOW_PLAYING_BUTTOMTITLE', { + tracksQueue += context.t('commands:MESSAGE_NOW_PLAYING_BUTTOMTITLE', { length: tracks.length - 10 }); } @@ -183,12 +183,12 @@ export class RemoveCommand extends BaseCommand { } const methods = [ - client.i18n.t('commands:REPEAT_MODE_OFF'), - client.i18n.t('commands:REPEAT_MODE_SINGLE'), - client.i18n.t('commands:REPEAT_MODE_ALL') + context.t('commands:REPEAT_MODE_OFF'), + context.t('commands:REPEAT_MODE_SINGLE'), + context.t('commands:REPEAT_MODE_ALL') ]; const repeatMode = player.repeatMode; - const instruction = client.i18n.t('commands:MESSAGE_REMOVE_INSTRUCTION', { + const instruction = context.t('commands:MESSAGE_REMOVE_INSTRUCTION', { length: tracks.length }); @@ -198,7 +198,7 @@ export class RemoveCommand extends BaseCommand { const msg = await context.reply({ content: instruction, - embeds: [embeds.removeList(bot, nowplaying, tracksQueue, methods[repeatMode])], + embeds: [embeds.removeList(bot, nowplaying, tracksQueue, methods[repeatMode], context.language)], allowedMentions: { repliedUser: false } }); @@ -214,13 +214,13 @@ export class RemoveCommand extends BaseCommand { if (!index || index <= 0 || index > tracks.length) { if (context.isMessage()) { await context.reply({ - embeds: [embeds.textWarningMsg(bot, client.i18n.t('commands:MESSAGE_REMOVE_CANCEL'))], + embeds: [embeds.textWarningMsg(bot, context.t('commands:MESSAGE_REMOVE_CANCEL'))], allowedMentions: { repliedUser: false } }); } else { await context.reply({ - embeds: [embeds.textWarningMsg(bot, client.i18n.t('commands:MESSAGE_REMOVE_CANCEL'))], + embeds: [embeds.textWarningMsg(bot, context.t('commands:MESSAGE_REMOVE_CANCEL'))], allowedMentions: { repliedUser: false } }); } @@ -236,7 +236,7 @@ export class RemoveCommand extends BaseCommand { } await query.reply({ - embeds: [embeds.removeTrack(bot, tracks[index - 1])], + embeds: [embeds.removeTrack(bot, tracks[index - 1], context.language)], allowedMentions: { repliedUser: false } }); @@ -250,7 +250,7 @@ export class RemoveCommand extends BaseCommand { collector.on('end', async (collected: ReadonlyCollection>, reason: string) => { if (reason === 'time' && collected.size === 0) { await msg.edit({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_TIME_EXPIRED'))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_TIME_EXPIRED'))], allowedMentions: { repliedUser: false } }).catch(() => bot.logger.discord( bot.shardId, 'Failed to edit deleted message.') diff --git a/src/commands/ResumeCommand.ts b/src/commands/ResumeCommand.ts index 2cae0a9..8ead433 100644 --- a/src/commands/ResumeCommand.ts +++ b/src/commands/ResumeCommand.ts @@ -27,12 +27,12 @@ export class ResumeCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } if (!player.paused) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_RESUME_MUSIC_RESUMED')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_RESUME_MUSIC_RESUMED')); return; } @@ -43,10 +43,10 @@ export class ResumeCommand extends BaseCommand { } else { if (SUCCESS) { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_RESUME_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_RESUME_SUCCESS')); } else { - await context.replyError(bot, client.i18n.t('commands:MESSAGE_RESUME_FAIL')); + await context.replyError(bot, context.t('commands:MESSAGE_RESUME_FAIL')); } } } diff --git a/src/commands/SearchCommand.ts b/src/commands/SearchCommand.ts index f08ee00..e083515 100644 --- a/src/commands/SearchCommand.ts +++ b/src/commands/SearchCommand.ts @@ -49,7 +49,7 @@ export class SearchCommand extends BaseCommand { : context.getStringOption('search'); if (!str) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_PLAY_ARGS_ERROR')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_PLAY_ARGS_ERROR')); return; } @@ -60,7 +60,7 @@ export class SearchCommand extends BaseCommand { } catch (error) { console.error(error); bot.logger.error( bot.shardId, `Search Error: ${error}`); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_SEARCH', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_SEARCH', { reason: error instanceof Error ? error.message : String(error) })); return; @@ -69,13 +69,13 @@ export class SearchCommand extends BaseCommand { // Handle search results if (res.loadType === LoadType.ERROR) { bot.logger.error( bot.shardId, `Search Error: ${JSON.stringify(res)}`); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_SEARCH', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_SEARCH', { reason: (res as any).data?.message })); return; } else if (res.loadType === LoadType.EMPTY) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_PLAY_SEARCH_NO_MATCH')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_PLAY_SEARCH_NO_MATCH')); return; } @@ -87,7 +87,7 @@ export class SearchCommand extends BaseCommand { const validBlackist = isUserInBlacklist(voiceChannel, bot.config.blacklist, bot.blacklistManager); if (validBlackist.length > 0) { await context.reply({ - embeds: [embeds.blacklist(bot, validBlackist)] + embeds: [embeds.blacklist(bot, validBlackist, context.language)] }); return; } @@ -149,7 +149,7 @@ export class SearchCommand extends BaseCommand { player.metadata = metadata; } catch (error) { bot.logger.error( bot.shardId, 'Error joining channel: ' + error); - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_PLAY_JOIN_CHANNEL')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_PLAY_JOIN_CHANNEL')); return null; } @@ -191,7 +191,7 @@ export class SearchCommand extends BaseCommand { const playlistCheck = QueueLimitManager.calculatePlaylistAddition(bot, player, userId, guildMember, playlistSize); if (playlistCheck.limitReached) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_QUEUE_LIMIT_REACHED', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_QUEUE_LIMIT_REACHED', { current: QueueLimitManager.countUserSongsInQueue(player, userId), limit: QueueLimitManager.getUserLimit(bot, userId, guildMember, player) })); @@ -210,7 +210,7 @@ export class SearchCommand extends BaseCommand { await player.play() .catch(async (error) => { bot.logger.error( bot.shardId, 'Error playing track: ' + error); - await context.replyError(bot, client.i18n.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); + await context.replyError(bot, context.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); return player.destroy(); }); } @@ -220,14 +220,14 @@ export class SearchCommand extends BaseCommand { const currentCount = QueueLimitManager.countUserSongsInQueue(player, userId); const limit = QueueLimitManager.getUserLimit(bot, userId, guildMember, player); - await context.replyWarning(bot, client.i18n.t('commands:MESSAGE_PLAYLIST_PARTIAL', { + await context.replyWarning(bot, context.t('commands:MESSAGE_PLAYLIST_PARTIAL', { added: playlistCheck.canAddCount, skipped: playlistCheck.willSkipCount, current: currentCount, limit: limit })); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_PLAY_MUSIC_ADD')); + await context.replySuccess(bot, context.t('commands:MESSAGE_PLAY_MUSIC_ADD')); } } @@ -243,7 +243,7 @@ export class SearchCommand extends BaseCommand { const checkResult = QueueLimitManager.canAddSongs(bot, player, userId, guildMember, 1); if (!checkResult.canAdd) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_QUEUE_LIMIT_REACHED', { + await context.replyEphemeralError(bot, context.t('commands:ERROR_QUEUE_LIMIT_REACHED', { current: checkResult.currentCount, limit: checkResult.limit })); @@ -261,12 +261,12 @@ export class SearchCommand extends BaseCommand { await player.play() .catch(async (error) => { bot.logger.error( bot.shardId, 'Error playing track: ' + error); - await context.replyError(bot, client.i18n.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); + await context.replyError(bot, context.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) })); return player.destroy(); }); } - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_PLAY_MUSIC_ADD')); + await context.replySuccess(bot, context.t('commands:MESSAGE_PLAY_MUSIC_ADD')); } /** @@ -279,11 +279,11 @@ export class SearchCommand extends BaseCommand { const select = new StringSelectMenuBuilder() .setCustomId(SelectButtonId.Music) - .setPlaceholder(client.i18n.t('commands:MESSAGE_PLAY_SELECT_TITLE')) + .setPlaceholder(context.t('commands:MESSAGE_PLAY_SELECT_TITLE')) .setOptions(res.tracks.map((x: any) => { return { label: x.title.length >= 25 ? x.title.substring(0, 22) + '...' : x.title, - description: client.i18n.t('commands:MESSAGE_PLAY_SELECT_DURATION', { label: x.duration.label }), + description: context.t('commands:MESSAGE_PLAY_SELECT_DURATION', { label: x.duration.label }), value: x.uri }; })); @@ -305,7 +305,7 @@ export class SearchCommand extends BaseCommand { if (!checkResult.canAdd) { await i.deferUpdate(); await msg.edit({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_QUEUE_LIMIT_REACHED', { + embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_QUEUE_LIMIT_REACHED', { current: checkResult.currentCount, limit: checkResult.limit }))], @@ -326,7 +326,7 @@ export class SearchCommand extends BaseCommand { bot.logger.error( bot.shardId, 'Error playing track: ' + error); await context.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) }))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_PLAY_MUSIC', { reason: JSON.stringify(error) }))], components: [], allowedMentions: { repliedUser: false } }); @@ -338,7 +338,7 @@ export class SearchCommand extends BaseCommand { await i.deferUpdate(); await msg.edit({ - embeds: [embeds.textSuccessMsg(bot, client.i18n.t('commands:MESSAGE_PLAY_MUSIC_ADD'))], + embeds: [embeds.textSuccessMsg(bot, context.t('commands:MESSAGE_PLAY_MUSIC_ADD'))], components: [] }); }); @@ -350,7 +350,7 @@ export class SearchCommand extends BaseCommand { } await msg.edit({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_TIME_EXPIRED'))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_TIME_EXPIRED'))], components: [] }); } diff --git a/src/commands/SeekCommand.ts b/src/commands/SeekCommand.ts index afc6141..9fa19ed 100644 --- a/src/commands/SeekCommand.ts +++ b/src/commands/SeekCommand.ts @@ -36,7 +36,7 @@ export class SeekCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -55,7 +55,7 @@ export class SeekCommand extends BaseCommand { const canDJBypass = bot.config.command.requesterDjBypass.includes('seek') && isDJ; if (!isRequester && !isAdmin && !canDJBypass) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_SEEK_NOT_REQUESTER')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_SEEK_NOT_REQUESTER')); return; } } @@ -68,7 +68,7 @@ export class SeekCommand extends BaseCommand { const targetTime = timeToSeconds(str); if (targetTime === false || targetTime < 0) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_SEEK_ARGS_ERROR')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_SEEK_ARGS_ERROR')); return; } @@ -82,12 +82,12 @@ export class SeekCommand extends BaseCommand { await player.seek(targetTimeMs); if (targetTimeMs >= trackDuration.value) { - await context.replyWarning(bot, client.i18n.t('commands:MESSAGE_SEEK_SKIP', { + await context.replyWarning(bot, context.t('commands:MESSAGE_SEEK_SKIP', { duration: trackDuration.label })); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_SEEK_SUCCESS', { + await context.replySuccess(bot, context.t('commands:MESSAGE_SEEK_SUCCESS', { duration: str })); } diff --git a/src/commands/ServerCommand.ts b/src/commands/ServerCommand.ts index b73317b..d9fb092 100644 --- a/src/commands/ServerCommand.ts +++ b/src/commands/ServerCommand.ts @@ -27,7 +27,7 @@ export class ServerCommand extends BaseCommand { protected async run(bot: Bot, client: Client, context: CommandContext): Promise { const serverlist = client.guilds.cache - .map(g => `${bot.i18n.t('commands:MESSAGE_SERVER_GUILD_ID')}: ${g.id}\n ${bot.i18n.t('commands:MESSAGE_SERVER_GUILD')}: ${g.name}\n ${bot.i18n.t('commands:MESSAGE_SERVER_MEMBERS')}: ${g.memberCount}`) + .map(g => `${context.t('commands:MESSAGE_SERVER_GUILD_ID')}: ${g.id}\n ${context.t('commands:MESSAGE_SERVER_GUILD')}: ${g.name}\n ${context.t('commands:MESSAGE_SERVER_MEMBERS')}: ${g.memberCount}`) .join('\n\n'); // Get DJ information @@ -37,12 +37,12 @@ export class ServerCommand extends BaseCommand { // Format DJ role const djRoleText = bot.config.bot.djRoleId ? `<@&${bot.config.bot.djRoleId}>` - : bot.i18n.t('commands:MESSAGE_DJ_ROLE_NOT_SET'); + : context.t('commands:MESSAGE_DJ_ROLE_NOT_SET'); // Format admins const adminsText = djInfo.admins.length > 0 ? djInfo.admins.map(id => `<@${id}>`).join(', ') - : bot.i18n.t('commands:MESSAGE_NONE'); + : context.t('commands:MESSAGE_NONE'); // Format all DJs (static, role-based, and dynamic) const allDJs: string[] = []; @@ -50,26 +50,26 @@ export class ServerCommand extends BaseCommand { // Add static DJs from config djInfo.staticDJs.forEach(id => { if (!djInfo.admins.includes(id)) { - allDJs.push(`<@${id}> ${bot.i18n.t('commands:MESSAGE_DJ_TYPE_STATIC')}`); + allDJs.push(`<@${id}> ${context.t('commands:MESSAGE_DJ_TYPE_STATIC')}`); } }); // Add role-based DJs djInfo.roleDJs.forEach(id => { - allDJs.push(`<@${id}> ${bot.i18n.t('commands:MESSAGE_DJ_TYPE_ROLE')}`); + allDJs.push(`<@${id}> ${context.t('commands:MESSAGE_DJ_TYPE_ROLE')}`); }); // Add dynamic DJs djInfo.dynamicDJs.forEach(id => { if (!djInfo.admins.includes(id) && !djInfo.staticDJs.includes(id)) { - allDJs.push(`<@${id}> ${bot.i18n.t('commands:MESSAGE_DJ_TYPE_DYNAMIC')}`); + allDJs.push(`<@${id}> ${context.t('commands:MESSAGE_DJ_TYPE_DYNAMIC')}`); } }); - const djUsersText = allDJs.length > 0 ? allDJs.join(', ') : bot.i18n.t('commands:MESSAGE_NONE'); + const djUsersText = allDJs.length > 0 ? allDJs.join(', ') : context.t('commands:MESSAGE_NONE'); await context.reply({ - embeds: [embeds.server(bot, serverlist, djRoleText, adminsText, djUsersText)], + embeds: [embeds.server(bot, serverlist, djRoleText, adminsText, djUsersText, context.language)], allowedMentions: { repliedUser: false } }); } diff --git a/src/commands/ShuffleCommand.ts b/src/commands/ShuffleCommand.ts index 67b754c..5c5ffe9 100644 --- a/src/commands/ShuffleCommand.ts +++ b/src/commands/ShuffleCommand.ts @@ -27,7 +27,7 @@ export class ShuffleCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -41,7 +41,7 @@ export class ShuffleCommand extends BaseCommand { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_SHUFFLE_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_SHUFFLE_SUCCESS')); } } } diff --git a/src/commands/SkipCommand.ts b/src/commands/SkipCommand.ts index 7c32371..f106190 100644 --- a/src/commands/SkipCommand.ts +++ b/src/commands/SkipCommand.ts @@ -31,7 +31,7 @@ export class SkipCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guildId!); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -55,7 +55,7 @@ export class SkipCommand extends BaseCommand { // Deny skip if user is not requester and doesn't have bypass permissions if (!isRequester && !isAdmin && !canDJBypass) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_SKIP_NOT_REQUESTER')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_SKIP_NOT_REQUESTER')); return; } } @@ -67,10 +67,10 @@ export class SkipCommand extends BaseCommand { } else { if (success) { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_SKIP_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_SKIP_SUCCESS')); } else { - await context.replyError(bot, client.i18n.t('commands:MESSAGE_SKIP_FAIL')); + await context.replyError(bot, context.t('commands:MESSAGE_SKIP_FAIL')); } } } diff --git a/src/commands/StatusCommand.ts b/src/commands/StatusCommand.ts index 85ee341..fb6c03c 100644 --- a/src/commands/StatusCommand.ts +++ b/src/commands/StatusCommand.ts @@ -43,10 +43,10 @@ export class StatusCommand extends BaseCommand { if (ping === -1) { unhealthValue++; - nodesStatus.push({ name: `āŒ ${node.identifier}`, value: bot.i18n.t('embeds:NODE_DISCONNECTED') }); + nodesStatus.push({ name: `āŒ ${node.identifier}`, value: context.t('embeds:NODE_DISCONNECTED') }); } else { - nodesStatus.push({ name: `āœ… ${node.identifier}`, value: bot.i18n.t('embeds:NODE_CONNECTED', { ping: ping }) }); + nodesStatus.push({ name: `āœ… ${node.identifier}`, value: context.t('embeds:NODE_CONNECTED', { ping: ping }) }); } } @@ -96,8 +96,8 @@ export class StatusCommand extends BaseCommand { await context.reply({ embeds: [ - embeds.botStatus(bot, systemStatus), - embeds.nodesStatus(bot, unhealthValue, nodesStatus) + embeds.botStatus(bot, systemStatus, context.language), + embeds.nodesStatus(bot, unhealthValue, nodesStatus, context.language) ], allowedMentions: { repliedUser: false } }); diff --git a/src/commands/StopCommand.ts b/src/commands/StopCommand.ts index e08fecd..8dd5a02 100644 --- a/src/commands/StopCommand.ts +++ b/src/commands/StopCommand.ts @@ -27,7 +27,7 @@ export class StopCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -45,7 +45,7 @@ export class StopCommand extends BaseCommand { await context.react('šŸ‘'); } else { - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_STOP_SUCCESS')); + await context.replySuccess(bot, context.t('commands:MESSAGE_STOP_SUCCESS')); } } } diff --git a/src/commands/VolumeCommand.ts b/src/commands/VolumeCommand.ts index 3ceb5b2..6fcf065 100644 --- a/src/commands/VolumeCommand.ts +++ b/src/commands/VolumeCommand.ts @@ -45,7 +45,7 @@ export class VolumeCommand extends BaseCommand { const player = client.lavashark.getPlayer(context.guild!.id); if (!player || !player.playing) { - await context.replyEphemeralError(bot, client.i18n.t('commands:ERROR_NO_PLAYING')); + await context.replyEphemeralError(bot, context.t('commands:ERROR_NO_PLAYING')); return; } @@ -95,7 +95,7 @@ export class VolumeCommand extends BaseCommand { .addComponents(volume25Button, volume50Button, volume75Button, volume100Button); const msg = await context.reply({ - embeds: [embeds.textMsg(bot, client.i18n.t('commands:MESSAGE_VOLUME_SELECT', { volume: currentVolume }))], + embeds: [embeds.textMsg(bot, context.t('commands:MESSAGE_VOLUME_SELECT', { volume: currentVolume }))], components: [row] }); @@ -118,7 +118,7 @@ export class VolumeCommand extends BaseCommand { if (player.volume === newVolume) { await i.update({ - embeds: [embeds.textWarningMsg(bot, client.i18n.t('commands:MESSAGE_VOLUME_SAME'))], + embeds: [embeds.textWarningMsg(bot, context.t('commands:MESSAGE_VOLUME_SAME'))], components: [] }); return collector.stop(); @@ -126,7 +126,7 @@ export class VolumeCommand extends BaseCommand { if (newVolume > maxVolume) { await i.update({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:MESSAGE_VOLUME_ARGS_ERROR_2', { maxVolume }))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:MESSAGE_VOLUME_ARGS_ERROR_2', { maxVolume }))], components: [] }); return collector.stop(); @@ -138,7 +138,7 @@ export class VolumeCommand extends BaseCommand { await client.dashboard.update(player, player.current!); await i.update({ - embeds: [embeds.textSuccessMsg(bot, client.i18n.t('commands:MESSAGE_VOLUME_SUCCESS', { volume: newVolume, maxVolume }))], + embeds: [embeds.textSuccessMsg(bot, context.t('commands:MESSAGE_VOLUME_SUCCESS', { volume: newVolume, maxVolume }))], components: [] }); @@ -148,7 +148,7 @@ export class VolumeCommand extends BaseCommand { collector.on('end', async (collected: Collection, reason: string) => { if (reason === 'time' && collected.size === 0) { await msg.edit({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_TIME_EXPIRED'))], + embeds: [embeds.textErrorMsg(bot, context.t('commands:ERROR_TIME_EXPIRED'))], components: [] }) .catch(() => bot.logger.discord( bot.shardId, 'Failed to edit deleted message.')); @@ -166,17 +166,17 @@ export class VolumeCommand extends BaseCommand { } if (!vol) { - await context.replyError(bot, client.i18n.t('commands:MESSAGE_VOLUME_ARGS_ERROR', { volume: player.volume, maxVolume })); + await context.replyError(bot, context.t('commands:MESSAGE_VOLUME_ARGS_ERROR', { volume: player.volume, maxVolume })); return; } if (player.volume === vol) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_VOLUME_SAME')); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_VOLUME_SAME')); return; } if (vol < 0 || vol > maxVolume) { - await context.replyEphemeralError(bot, client.i18n.t('commands:MESSAGE_VOLUME_ARGS_ERROR_2', { maxVolume })); + await context.replyEphemeralError(bot, context.t('commands:MESSAGE_VOLUME_ARGS_ERROR_2', { maxVolume })); return; } @@ -185,6 +185,6 @@ export class VolumeCommand extends BaseCommand { await client.dashboard.update(player, player.current!); - await context.replySuccess(bot, client.i18n.t('commands:MESSAGE_VOLUME_SUCCESS', { volume: vol, maxVolume })); + await context.replySuccess(bot, context.t('commands:MESSAGE_VOLUME_SUCCESS', { volume: vol, maxVolume })); } } diff --git a/src/commands/base/CommandContext.ts b/src/commands/base/CommandContext.ts index 2745b6f..2ae372a 100644 --- a/src/commands/base/CommandContext.ts +++ b/src/commands/base/CommandContext.ts @@ -15,10 +15,12 @@ import type { Bot } from '../../@types/index.js'; * Unified command context that abstracts Message and Interaction differences */ export class CommandContext { + public readonly bot: Bot; readonly #source: Message | ChatInputCommandInteraction; #args: string[]; - constructor(source: Message | ChatInputCommandInteraction, args: string[] = []) { + constructor(bot: Bot, source: Message | ChatInputCommandInteraction, args: string[] = []) { + this.bot = bot; this.#source = source; this.#args = args; } @@ -114,6 +116,21 @@ export class CommandContext { return this.#source.guildId; } + /** + * Get the language for the current guild + */ + public get language(): string { + if (!this.guildId) return this.bot.config.bot.i18n.defaultLocale; + return this.bot.guildLanguageManager?.get(this.guildId) || this.bot.config.bot.i18n.defaultLocale; + } + + /** + * Translate a key using the guild's language + */ + public t(key: string, options?: any): string { + return this.bot.i18n.t(key, { ...options, lng: this.language }) as string; + } + /** * Send typing indicator */ diff --git a/src/embeds/dashboard.embed.ts b/src/embeds/dashboard.embed.ts index e837ec9..e410ef5 100644 --- a/src/embeds/dashboard.embed.ts +++ b/src/embeds/dashboard.embed.ts @@ -2,18 +2,18 @@ import { EmbedBuilder, HexColorString } from 'discord.js'; import type { Bot } from '../@types/index.js'; -const connected = (bot: Bot) => { +const connected = (bot: Bot, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.success as HexColorString | number) - .setDescription(bot.i18n.t('embeds:DASHBOARD_VOICE_CHANNEL_CONNECT_SUCCESS')); + .setDescription(bot.i18n.t('embeds:DASHBOARD_VOICE_CHANNEL_CONNECT_SUCCESS', { lng })); return embed_; }; -const disconnect = (bot: Bot) => { +const disconnect = (bot: Bot, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setDescription(bot.i18n.t('embeds:DASHBOARD_FINISH_PLAYING')); + .setDescription(bot.i18n.t('embeds:DASHBOARD_FINISH_PLAYING', { lng })); return embed_; }; diff --git a/src/embeds/msg.embed.ts b/src/embeds/msg.embed.ts index 29e60da..d9ea7ac 100644 --- a/src/embeds/msg.embed.ts +++ b/src/embeds/msg.embed.ts @@ -2,10 +2,10 @@ import { EmbedBuilder, HexColorString } from 'discord.js'; import type { Bot } from '../@types/index.js'; -const blacklist = (bot: Bot, userList: { name: string; value: string; }[]) => { +const blacklist = (bot: Bot, userList: { name: string; value: string; }[], lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setTitle(bot.i18n.t('embeds:MESSAGE_BLACKLIST')) + .setTitle(bot.i18n.t('embeds:MESSAGE_BLACKLIST', { lng })) .setDescription('━━━━━━━━━━━━━━━━━━━━━━━━━━━━') .addFields(userList) .setTimestamp(); @@ -13,18 +13,18 @@ const blacklist = (bot: Bot, userList: { name: string; value: string; }[]) => { return embed_; }; -const filterMsg = (bot: Bot, effectName: string) => { +const filterMsg = (bot: Bot, effectName: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setDescription(bot.i18n.t('embeds:MESSAGE_FILTER', { effectName: effectName })); + .setDescription(bot.i18n.t('embeds:MESSAGE_FILTER', { effectName: effectName, lng })); return embed_; }; -const help = (bot: Bot, command: string, description: string) => { +const help = (bot: Bot, command: string, description: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setTitle(bot.i18n.t('embeds:MESSAGE_COMMAND', { command: command })) + .setTitle(bot.i18n.t('embeds:MESSAGE_COMMAND', { command: command, lng })) .setDescription(description); return embed_; diff --git a/src/embeds/ping.embed.ts b/src/embeds/ping.embed.ts index a2052c5..f39e79d 100644 --- a/src/embeds/ping.embed.ts +++ b/src/embeds/ping.embed.ts @@ -2,11 +2,11 @@ import { EmbedBuilder, HexColorString } from 'discord.js'; import type { Bot } from '../@types/index.js'; -const ping = (bot: Bot, botPing: string, apiPing: string) => { +const ping = (bot: Bot, botPing: string, apiPing: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setTitle(bot.i18n.t('embeds:PING_TITLE')) - .setDescription(bot.i18n.t('embeds:PING_DESCRIPTION', { botPing: botPing, apiPing: apiPing })); + .setTitle(bot.i18n.t('embeds:PING_TITLE', { lng })) + .setDescription(bot.i18n.t('embeds:PING_DESCRIPTION', { botPing: botPing, apiPing: apiPing, lng })); return embed_; }; diff --git a/src/embeds/queue.embed.ts b/src/embeds/queue.embed.ts index 44165fb..6883f0c 100644 --- a/src/embeds/queue.embed.ts +++ b/src/embeds/queue.embed.ts @@ -2,37 +2,37 @@ import { EmbedBuilder, HexColorString } from 'discord.js'; import type { Bot } from '../@types/index.js'; -const addTrack = (bot: Bot, title: string, subtitle: string, url: string, thumbnail: string) => { +const addTrack = (bot: Bot, title: string, subtitle: string, url: string, thumbnail: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) .setTitle(title) .setURL(url) .setThumbnail(thumbnail) - .addFields({ name: bot.i18n.t('embeds:QUEUE_ADD_TRACK'), value: subtitle, inline: true }) + .addFields({ name: bot.i18n.t('embeds:QUEUE_ADD_TRACK', { lng }), value: subtitle, inline: true }) .setTimestamp(); return embed_; }; -const addPlaylist = (bot: Bot, title: string, subtitle: string, url: string, thumbnail: string) => { +const addPlaylist = (bot: Bot, title: string, subtitle: string, url: string, thumbnail: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) .setTitle(title) .setURL(url) .setThumbnail(thumbnail) - .addFields({ name: bot.i18n.t('embeds:QUEUE_ADD_PLAYLIST'), value: subtitle, inline: true }) + .addFields({ name: bot.i18n.t('embeds:QUEUE_ADD_PLAYLIST', { lng }), value: subtitle, inline: true }) .setTimestamp(); return embed_; }; -const queue = (bot: Bot, description: string, repeatMode: string) => { +const queue = (bot: Bot, description: string, repeatMode: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setTitle(bot.i18n.t('embeds:QUEUE_LIST_TITLE')) + .setTitle(bot.i18n.t('embeds:QUEUE_LIST_TITLE', { lng })) .setDescription(description) .setTimestamp() - .setFooter({ text: bot.i18n.t('embeds:QUEUE_LIST_LOOP_MODE', { repeatMode: repeatMode }) }); + .setFooter({ text: bot.i18n.t('embeds:QUEUE_LIST_LOOP_MODE', { repeatMode: repeatMode, lng }) }); return embed_; }; diff --git a/src/embeds/remove.embed.ts b/src/embeds/remove.embed.ts index 912ded6..ed3b2be 100644 --- a/src/embeds/remove.embed.ts +++ b/src/embeds/remove.embed.ts @@ -2,21 +2,21 @@ import { EmbedBuilder, HexColorString } from 'discord.js'; import type { Bot } from '../@types/index.js'; -const removeList = (bot: Bot, nowPlaying: string, queueList: string, repeatMode: string) => { +const removeList = (bot: Bot, nowPlaying: string, queueList: string, repeatMode: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setTitle(bot.i18n.t('embeds:REMOVE_LIST_TITLE')) + .setTitle(bot.i18n.t('embeds:REMOVE_LIST_TITLE', { lng })) .addFields({ name: nowPlaying, value: queueList }) .setTimestamp() - .setFooter({ text: bot.i18n.t('embeds:REMOVE_LIST_LOOP_MODE', { repeatMode: repeatMode }) }); + .setFooter({ text: bot.i18n.t('embeds:REMOVE_LIST_LOOP_MODE', { repeatMode: repeatMode, lng }) }); return embed_; }; -const removeTrack = (bot: Bot, musicTitle: string) => { +const removeTrack = (bot: Bot, musicTitle: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.success as HexColorString | number) - .setTitle(bot.i18n.t('embeds:REMOVE_TRACK_TITLE')) + .setTitle(bot.i18n.t('embeds:REMOVE_TRACK_TITLE', { lng })) .setDescription(musicTitle) .setTimestamp(); diff --git a/src/embeds/server.embed.ts b/src/embeds/server.embed.ts index cb8c4a3..87ed239 100644 --- a/src/embeds/server.embed.ts +++ b/src/embeds/server.embed.ts @@ -3,21 +3,21 @@ import type { Bot } from '../@types/index.js'; -const server = (bot: Bot, serverlist: string, djRole?: string, admins?: string, djUsers?: string) => { +const server = (bot: Bot, serverlist: string, djRole?: string, admins?: string, djUsers?: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.message as HexColorString | number) - .setTitle(bot.i18n.t('embeds:SERVER_TITLE', {name: bot.config.bot.name})) + .setTitle(bot.i18n.t('embeds:SERVER_TITLE', { name: bot.config.bot.name, lng })) .setDescription(serverlist); // Add DJ information fields if provided if (djRole) { - embed_.addFields({ name: bot.i18n.t('embeds:SERVER_FIELD_DJ_ROLE'), value: djRole, inline: true }); + embed_.addFields({ name: bot.i18n.t('embeds:SERVER_FIELD_DJ_ROLE', { lng }), value: djRole, inline: true }); } if (admins) { - embed_.addFields({ name: bot.i18n.t('embeds:SERVER_FIELD_ADMINS'), value: admins, inline: true }); + embed_.addFields({ name: bot.i18n.t('embeds:SERVER_FIELD_ADMINS', { lng }), value: admins, inline: true }); } if (djUsers) { - embed_.addFields({ name: bot.i18n.t('embeds:SERVER_FIELD_DJ_USERS'), value: djUsers, inline: false }); + embed_.addFields({ name: bot.i18n.t('embeds:SERVER_FIELD_DJ_USERS', { lng }), value: djUsers, inline: false }); } return embed_; diff --git a/src/embeds/status.embed.ts b/src/embeds/status.embed.ts index 17bed03..efc555f 100644 --- a/src/embeds/status.embed.ts +++ b/src/embeds/status.embed.ts @@ -6,7 +6,7 @@ import type { Info, NodeStats } from 'lavashark/typings/src/@types/Node.types.js import type { SystemStatus } from '../@types/index.js'; -const botStatus = (bot: Bot, systemStatus: SystemStatus) => { +const botStatus = (bot: Bot, systemStatus: SystemStatus, lng?: string) => { const cpuUsage = `${systemStatus.load.percent} \`${systemStatus.load.detail}\``; const ramUsage = `${systemStatus.memory.percent} \`${systemStatus.memory.detail}\``; const heapUsage = `${systemStatus.heap.percent} \`${systemStatus.heap.detail}\``; @@ -15,34 +15,34 @@ const botStatus = (bot: Bot, systemStatus: SystemStatus) => { .setColor(bot.config.bot.embedsColors.message as HexColorString | number) .setTitle(`${bot.config.bot.name} ${bot.sysInfo.bot_version}`) .setURL('https://github.com/hmes98318/Music-Disc') - .setDescription(bot.i18n.t('embeds:STATUS_DESCRIPTION', { serverCount: systemStatus.serverCount, playingCount: systemStatus.playing })) + .setDescription(bot.i18n.t('embeds:STATUS_DESCRIPTION', { serverCount: systemStatus.serverCount, playingCount: systemStatus.playing, lng })) .addFields( - { name: bot.i18n.t('embeds:STATUS_SYSTEM_TITLE'), value: bot.i18n.t('embeds:STATUS_SYSTEM_VALUE', { os_version: bot.sysInfo.os_version, node_version: bot.sysInfo.node_version, dc_version: bot.sysInfo.dc_version, shark_version: bot.sysInfo.shark_version, cpu: bot.sysInfo.cpu, uptime: systemStatus.uptime }), inline: false }, - { name: bot.i18n.t('embeds:STATUS_USAGE_TITLE'), value: bot.i18n.t('embeds:STATUS_USAGE_VALUE', { cpuUsage: cpuUsage, ramUsage: ramUsage, heapUsage: heapUsage }), inline: false }, - { name: bot.i18n.t('embeds:STATUS_LATENCY_TITLE'), value: bot.i18n.t('embeds:STATUS_LATENCY_VALUE', { botPing: systemStatus.ping.bot, apiPing: systemStatus.ping.api }), inline: false } + { name: bot.i18n.t('embeds:STATUS_SYSTEM_TITLE', { lng }), value: bot.i18n.t('embeds:STATUS_SYSTEM_VALUE', { os_version: bot.sysInfo.os_version, node_version: bot.sysInfo.node_version, dc_version: bot.sysInfo.dc_version, shark_version: bot.sysInfo.shark_version, cpu: bot.sysInfo.cpu, uptime: systemStatus.uptime, lng }), inline: false }, + { name: bot.i18n.t('embeds:STATUS_USAGE_TITLE', { lng }), value: bot.i18n.t('embeds:STATUS_USAGE_VALUE', { cpuUsage: cpuUsage, ramUsage: ramUsage, heapUsage: heapUsage, lng }), inline: false }, + { name: bot.i18n.t('embeds:STATUS_LATENCY_TITLE', { lng }), value: bot.i18n.t('embeds:STATUS_LATENCY_VALUE', { botPing: systemStatus.ping.bot, apiPing: systemStatus.ping.api, lng }), inline: false } ) .setTimestamp(); return embed_; }; -const maintainNotice = (bot: Bot) => { +const maintainNotice = (bot: Bot, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.warning as HexColorString | number) - .setTitle(bot.i18n.t('embeds:MAINTAIN_TITLE')) - .setDescription(bot.i18n.t('embeds:MAINTAIN_DESCRIPTION')) + .setTitle(bot.i18n.t('embeds:MAINTAIN_TITLE', { lng })) + .setDescription(bot.i18n.t('embeds:MAINTAIN_DESCRIPTION', { lng })) .setTimestamp(); return embed_; }; -const nodesStatus = (bot: Bot, unhealthValue: number, nodesStatus: { name: string; value: string; }[]) => { - const healthString = unhealthValue > 0 ? bot.i18n.t('embeds:NODE_UNHEALTHY', { unhealthValue: unhealthValue }) : bot.i18n.t('embeds:NODE_ALL_ACTIVE'); +const nodesStatus = (bot: Bot, unhealthValue: number, nodesStatus: { name: string; value: string; }[], lng?: string) => { + const healthString = unhealthValue > 0 ? bot.i18n.t('embeds:NODE_UNHEALTHY', { unhealthValue: unhealthValue, lng }) : bot.i18n.t('embeds:NODE_ALL_ACTIVE', { lng }); const embedColor = unhealthValue > 0 ? bot.config.bot.embedsColors.warning : bot.config.bot.embedsColors.success; const embed_ = new EmbedBuilder() .setColor(embedColor as HexColorString | number) - .setTitle(bot.i18n.t('embeds:NODE_STATUS_TITLE')) + .setTitle(bot.i18n.t('embeds:NODE_STATUS_TITLE', { lng })) .setDescription(`**${healthString}**\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━`) .addFields(nodesStatus) .setTimestamp(); @@ -50,35 +50,35 @@ const nodesStatus = (bot: Bot, unhealthValue: number, nodesStatus: { name: strin return embed_; }; -const nodeStatus = (bot: Bot, nodeName: string, nodeInfo: Info, nodeStats: NodeStats, nodePing: number) => { +const nodeStatus = (bot: Bot, nodeName: string, nodeInfo: Info, nodeStats: NodeStats, nodePing: number, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.success as HexColorString | number) - .setTitle(bot.i18n.t('embeds:NODE_STATUS_TITLE_2', { nodeName: nodeName })) + .setTitle(bot.i18n.t('embeds:NODE_STATUS_TITLE_2', { nodeName: nodeName, lng })) .setDescription(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━`) .addFields( - { name: bot.i18n.t('embeds:NODE_STATUS_INFO_TITLE'), value: bot.i18n.t('embeds:NODE_STATUS_INFO_VALUE', { version: nodeInfo.version.semver, jvm: nodeInfo.jvm, lavaplayer: nodeInfo.lavaplayer, git: nodeInfo.git.commit, buildTime: timestampToTime(nodeInfo.buildTime) }) }, - { name: bot.i18n.t('embeds:NODE_STATUS_STATS_TITLE'), value: bot.i18n.t('embeds:NODE_STATUS_STATS_VALUE', { uptime: msToTime(nodeStats.uptime), pingKey: bot.i18n.t('embeds:NODE_STATUS_PING'), nodePing: nodePing, playerCount: nodeStats.players, playingCount: nodeStats.playingPlayers }) }, - { name: bot.i18n.t('embeds:NODE_STATUS_CPU_TITLE'), value: bot.i18n.t('embeds:NODE_STATUS_CPU_VALUE', { cores: nodeStats.cpu.cores, systemLoad: nodeStats.cpu.systemLoad.toFixed(6), lavalinkLoad: nodeStats.cpu.lavalinkLoad.toFixed(6) }) }, - { name: bot.i18n.t('embeds:NODE_STATUS_MEMORY_TITLE'), value: bot.i18n.t('embeds:NODE_STATUS_MEMORY_VALUE', { used: formatBytes(nodeStats.memory.used), free: formatBytes(nodeStats.memory.free), allocated: formatBytes(nodeStats.memory.allocated), reservable: formatBytes(nodeStats.memory.reservable) }) }) + { name: bot.i18n.t('embeds:NODE_STATUS_INFO_TITLE', { lng }), value: bot.i18n.t('embeds:NODE_STATUS_INFO_VALUE', { version: nodeInfo.version.semver, jvm: nodeInfo.jvm, lavaplayer: nodeInfo.lavaplayer, git: nodeInfo.git.commit, buildTime: timestampToTime(nodeInfo.buildTime), lng }) }, + { name: bot.i18n.t('embeds:NODE_STATUS_STATS_TITLE', { lng }), value: bot.i18n.t('embeds:NODE_STATUS_STATS_VALUE', { uptime: msToTime(nodeStats.uptime), pingKey: bot.i18n.t('embeds:NODE_STATUS_PING', { lng }), nodePing: nodePing, playerCount: nodeStats.players, playingCount: nodeStats.playingPlayers, lng }) }, + { name: bot.i18n.t('embeds:NODE_STATUS_CPU_TITLE', { lng }), value: bot.i18n.t('embeds:NODE_STATUS_CPU_VALUE', { cores: nodeStats.cpu.cores, systemLoad: nodeStats.cpu.systemLoad.toFixed(6), lavalinkLoad: nodeStats.cpu.lavalinkLoad.toFixed(6), lng }) }, + { name: bot.i18n.t('embeds:NODE_STATUS_MEMORY_TITLE', { lng }), value: bot.i18n.t('embeds:NODE_STATUS_MEMORY_VALUE', { used: formatBytes(nodeStats.memory.used), free: formatBytes(nodeStats.memory.free), allocated: formatBytes(nodeStats.memory.allocated), reservable: formatBytes(nodeStats.memory.reservable), lng }) }) .setTimestamp(); return embed_; }; -const nodeDisconnected = (bot: Bot, nodeName: string) => { +const nodeDisconnected = (bot: Bot, nodeName: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.error as HexColorString | number) - .setTitle(bot.i18n.t('embeds:NODE_STATUS_TITLE_2', { nodeName: nodeName })) - .setDescription(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nāŒć…£${bot.i18n.t('embeds:NODE_DISCONNECTED')}`) + .setTitle(bot.i18n.t('embeds:NODE_STATUS_TITLE_2', { nodeName: nodeName, lng })) + .setDescription(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nāŒć…£${bot.i18n.t('embeds:NODE_DISCONNECTED', { lng })}`) .setTimestamp(); return embed_; }; -const validNodeName = (bot: Bot, nodesName: string) => { +const validNodeName = (bot: Bot, nodesName: string, lng?: string) => { const embed_ = new EmbedBuilder() .setColor(bot.config.bot.embedsColors.error as HexColorString | number) - .setTitle(bot.i18n.t('embeds:NODE_STATUS_ARGS_ERROR')) + .setTitle(bot.i18n.t('embeds:NODE_STATUS_ARGS_ERROR', { lng })) .setDescription(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${nodesName}`) .setTimestamp(); diff --git a/src/events/discord/InteractionCreateEvent.ts b/src/events/discord/InteractionCreateEvent.ts index 65ea91a..155dd4b 100644 --- a/src/events/discord/InteractionCreateEvent.ts +++ b/src/events/discord/InteractionCreateEvent.ts @@ -49,13 +49,14 @@ export class InteractionCreateEvent extends BaseDiscordEvent { if (!interaction.isButton()) return; + const lng = bot.guildLanguageManager?.get(interaction.guildId!); const guildMember = interaction.guild!.members.cache.get(interaction.user.id); const voiceChannel = guildMember?.voice.channel; // Validate voice channel if (!voiceChannel) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_NOT_IN_VOICE_CHANNEL'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_NOT_IN_VOICE_CHANNEL', { lng }))], flags: MessageFlags.Ephemeral, components: [] }).catch((error) => { @@ -66,7 +67,7 @@ export class InteractionCreateEvent extends BaseDiscordEvent { @@ -80,7 +81,7 @@ export class InteractionCreateEvent extends BaseDiscordEvent { bot.logger.error( bot.shardId, '[interactionCreate] Error reply: ' + error); @@ -154,10 +155,12 @@ export class InteractionCreateEvent extends BaseDiscordEvent { if (!interaction.isChatInputCommand()) return; + const lng = bot.guildLanguageManager?.get(interaction.guildId!); + // Check if slash commands are enabled if (!bot.config.bot.slashCommand) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_SLASH_NOT_ENABLE'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_SLASH_NOT_ENABLE', { lng }))], allowedMentions: { repliedUser: false } }).catch((error) => { bot.logger.error( bot.shardId, `[interactionCreate] Error reply: (${interaction.user.username} : /${interaction.commandName}) ${error}`); @@ -235,7 +238,7 @@ export class InteractionCreateEvent extends BaseDiscordEvent { if (!cacheValidation.valid) return; // Execute command - const context = new CommandContext(message, args); + const context = new CommandContext(bot, message, args); await cmd.execute(bot, client, context); } } diff --git a/src/events/discord/base/CommandValidator.ts b/src/events/discord/base/CommandValidator.ts index 5409406..1762c3a 100644 --- a/src/events/discord/base/CommandValidator.ts +++ b/src/events/discord/base/CommandValidator.ts @@ -25,17 +25,19 @@ export class CommandValidator { */ public static async validateMessageChannel( bot: Bot, - client: Client, + _client: Client, source: Message | ChatInputCommandInteraction, channelId: string ): Promise { if (bot.config.bot.specifyMessageChannel && bot.config.bot.specifyMessageChannel !== channelId) { const username = source instanceof Message ? source.author.username : source.user.username; const content = source instanceof Message ? source.content : `/${source.commandName}`; + const lng = bot.guildLanguageManager?.get(source.guildId!); await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:MESSAGE_SPECIFIC_CHANNEL_WARN', { - channelId: bot.config.bot.specifyMessageChannel + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:MESSAGE_SPECIFIC_CHANNEL_WARN', { + channelId: bot.config.bot.specifyMessageChannel, + lng }))], allowedMentions: { repliedUser: false } }).catch((error) => { @@ -53,7 +55,7 @@ export class CommandValidator { */ public static async validateAdminPermission( bot: Bot, - client: Client, + _client: Client, source: Message | ChatInputCommandInteraction, command: BaseCommand, userId: string @@ -64,9 +66,10 @@ export class CommandValidator { if (!bot.config.bot.admin.includes(userId)) { const username = source instanceof Message ? source.author.username : source.user.username; const content = source instanceof Message ? source.content : `/${source.commandName}`; + const lng = bot.guildLanguageManager?.get(source.guildId!); await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_REQUIRE_ADMIN'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_REQUIRE_ADMIN', { lng }))], allowedMentions: { repliedUser: false } }).catch((error) => { bot.logger.error( bot.shardId, `Error reply: (${username} : ${content}) ${error}`); @@ -99,9 +102,10 @@ export class CommandValidator { if (!PermissionManager.hasDJCommandPermission(bot, userId, member, player || undefined)) { const username = source instanceof Message ? source.author.username : source.user.username; const content = source instanceof Message ? source.content : `/${source.commandName}`; + const lng = bot.guildLanguageManager?.get(source.guildId!); await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_REQUIRE_DJ'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_REQUIRE_DJ', { lng }))], allowedMentions: { repliedUser: false } }).catch((error) => { bot.logger.error( bot.shardId, `Error reply: (${username} : ${content}) ${error}`); @@ -134,11 +138,12 @@ export class CommandValidator { const voiceChannel = member.voice.channel; const username = source instanceof Message ? source.author.username : source.user.username; const content = source instanceof Message ? source.content : `/${source.commandName}`; + const lng = bot.guildLanguageManager?.get(source.guildId!); // Check if user is in voice channel if (!voiceChannel) { await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_NOT_IN_VOICE_CHANNEL'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_NOT_IN_VOICE_CHANNEL', { lng }))], allowedMentions: { repliedUser: false } }).catch((error) => { bot.logger.error( bot.shardId, `Error reply: (${username} : ${content}) ${error}`); @@ -150,8 +155,9 @@ export class CommandValidator { // Check if user is in specified voice channel if (bot.config.bot.specifyVoiceChannel && voiceChannel.id !== bot.config.bot.specifyVoiceChannel) { await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERRPR_NOT_IN_SPECIFIC_VOICE_CHANNEL', { - channelId: bot.config.bot.specifyVoiceChannel + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERRPR_NOT_IN_SPECIFIC_VOICE_CHANNEL', { + channelId: bot.config.bot.specifyVoiceChannel, + lng }))], allowedMentions: { repliedUser: false } }).catch((error) => { @@ -165,7 +171,7 @@ export class CommandValidator { const guild = await client.guilds.fetch(guildId); if (guild.members.me?.voice.channel && voiceChannel.id !== guild.members.me.voice.channelId) { await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_NOT_IN_SAME_VOICE_CHANNEL'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_NOT_IN_SAME_VOICE_CHANNEL', { lng }))], allowedMentions: { repliedUser: false } }).catch((error) => { bot.logger.error( bot.shardId, `Error reply: (${username} : ${content}) ${error}`); @@ -188,6 +194,7 @@ export class CommandValidator { userId: string ): Promise { let guild; + const lng = bot.guildLanguageManager?.get(source.guildId!); // Ensure guild data is in cache try { @@ -195,7 +202,7 @@ export class CommandValidator { } catch (error) { bot.logger.error( bot.shardId, `Error fetching guild (${guildId}): ${error}`); await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_GET_GUILD_DATA_CACHE'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_GET_GUILD_DATA_CACHE', { lng }))], allowedMentions: { repliedUser: false } }); return { valid: false, errorSent: true }; @@ -207,7 +214,7 @@ export class CommandValidator { } catch (error) { bot.logger.error( bot.shardId, `Error fetching member (${userId}): ${error}`); await source.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_GET_GUILD_DATA_CACHE'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_GET_GUILD_DATA_CACHE', { lng }))], allowedMentions: { repliedUser: false } }); return { valid: false, errorSent: true }; diff --git a/src/events/lavashark/TrackAddEvent.ts b/src/events/lavashark/TrackAddEvent.ts index f0ab87e..a72ce2d 100644 --- a/src/events/lavashark/TrackAddEvent.ts +++ b/src/events/lavashark/TrackAddEvent.ts @@ -47,18 +47,20 @@ export class TrackAddEvent extends BaseLavaSharkEvent<'trackAdd'> { * Handle playlist addition * @private */ - async #handlePlaylistAdd(bot: Bot, client: Client, player: Player, playlist: Track[]): Promise { + async #handlePlaylistAdd(bot: Bot, _client: Client, player: Player, playlist: Track[]): Promise { const firstTrack = playlist[0]; if (!firstTrack) return; - const subtitle = client.i18n.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { + const lng = bot.guildLanguageManager?.get(player.guildId); + const subtitle = bot.i18n.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { author: firstTrack.author, - label: firstTrack.duration.label + label: firstTrack.duration.label, + lng }); await (player.metadata?.channel as any).send({ - embeds: [embeds.addPlaylist(bot, firstTrack.title, subtitle, firstTrack.uri, firstTrack.thumbnail!)] + embeds: [embeds.addPlaylist(bot, firstTrack.title, subtitle, firstTrack.uri, firstTrack.thumbnail!, lng)] }); } @@ -66,14 +68,16 @@ export class TrackAddEvent extends BaseLavaSharkEvent<'trackAdd'> { * Handle single track addition * @private */ - async #handleTrackAdd(bot: Bot, client: Client, player: Player, track: Track): Promise { - const subtitle = client.i18n.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { + async #handleTrackAdd(bot: Bot, _client: Client, player: Player, track: Track): Promise { + const lng = bot.guildLanguageManager?.get(player.guildId); + const subtitle = bot.i18n.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { author: track.author, - label: track.duration.label + label: track.duration.label, + lng }); await (player.metadata?.channel as any).send({ - embeds: [embeds.addTrack(bot, track.title, subtitle, track.uri, track.thumbnail!)] + embeds: [embeds.addTrack(bot, track.title, subtitle, track.uri, track.thumbnail!, lng)] }); } } diff --git a/src/lib/DashboardManager.ts b/src/lib/DashboardManager.ts index d8b6e40..91e2541 100644 --- a/src/lib/DashboardManager.ts +++ b/src/lib/DashboardManager.ts @@ -39,8 +39,10 @@ export class DashboardManager { throw new TypeError('Invalid Interaction or Message type'); } + const lng = this.#bot.guildLanguageManager?.get(player.guildId); + player.dashboardMsg = await (channel as any /* discord.js type error ? (v14.16.2) */).send({ - embeds: [embeds.connected(this.#bot)], + embeds: [embeds.connected(this.#bot, lng)], components: [] }); } @@ -54,14 +56,15 @@ export class DashboardManager { return; } - const subtitle = await this.#buildSubtitle(player, track); - const buttons = ButtonsBuilder.createDashboardButtons(player); + const lng = this.#bot.guildLanguageManager?.get(player.guildId); + const subtitle = await this.#buildSubtitle(player, track, lng); + const buttons = ButtonsBuilder.createDashboardButtons(player, lng); try { await player.dashboardMsg.edit({ embeds: [embeds.dashboard( this.#bot, - this.#bot.i18n.t('embeds:DASHBOARD_TITLE'), + this.#bot.i18n.t('embeds:DASHBOARD_TITLE', { lng }), track.title, subtitle, track.uri, @@ -82,9 +85,11 @@ export class DashboardManager { return; } + const lng = this.#bot.guildLanguageManager?.get(player.guildId); + try { await player.dashboardMsg.edit({ - embeds: [embeds.disconnect(this.#bot)], + embeds: [embeds.disconnect(this.#bot, lng)], components: [] }); } catch (error) { @@ -98,20 +103,21 @@ export class DashboardManager { /** * Build subtitle with track info, volume, repeat mode, and DJ info */ - async #buildSubtitle(player: Player, track: Track): Promise { - const repeatModeLabel = this.#getRepeatModeLabel(player.repeatMode); + async #buildSubtitle(player: Player, track: Track, lng?: string): Promise { + const repeatModeLabel = this.#getRepeatModeLabel(player.repeatMode, lng); let subtitle = this.#bot.i18n.t('embeds:DASHBOARD_SUBTITLE', { author: track.author, duration: track.duration.label, volume: player.volume, - repeatMode: repeatModeLabel + repeatMode: repeatModeLabel, + lng }); // Add requester info const requesterId = track.requester?.id; if (requesterId) { - subtitle += this.#bot.i18n.t('embeds:DASHBOARD_REQUESTER_INFO', { requesterId }); + subtitle += this.#bot.i18n.t('embeds:DASHBOARD_REQUESTER_INFO', { requesterId, lng }); } // Add Dynamic DJ info (only if DYNAMIC mode AND a DJ is assigned) @@ -119,7 +125,7 @@ export class DashboardManager { try { const guild = this.#client.guilds.cache.get(player.guildId); const djDisplay = await DJManager.getDJDisplayString(this.#bot, this.#client, guild, player); - subtitle += this.#bot.i18n.t('embeds:DASHBOARD_DJ_INFO', { djDisplay }); + subtitle += this.#bot.i18n.t('embeds:DASHBOARD_DJ_INFO', { djDisplay, lng }); } catch (_) { // Ignore errors in DJ display } @@ -131,11 +137,11 @@ export class DashboardManager { /** * Get repeat mode label for display */ - #getRepeatModeLabel(repeatMode: number): string { + #getRepeatModeLabel(repeatMode: number, lng?: string): string { const methods = [ - this.#bot.i18n.t('commands:REPEAT_MODE_OFF'), - this.#bot.i18n.t('commands:REPEAT_MODE_SINGLE'), - this.#bot.i18n.t('commands:REPEAT_MODE_ALL') + this.#bot.i18n.t('commands:REPEAT_MODE_OFF', { lng }), + this.#bot.i18n.t('commands:REPEAT_MODE_SINGLE', { lng }), + this.#bot.i18n.t('commands:REPEAT_MODE_ALL', { lng }) ]; return methods[repeatMode] || methods[0]; } diff --git a/src/lib/GuildLanguageManager.ts b/src/lib/GuildLanguageManager.ts new file mode 100644 index 0000000..a14985e --- /dev/null +++ b/src/lib/GuildLanguageManager.ts @@ -0,0 +1,85 @@ +import Database from 'better-sqlite3'; +import { existsSync, mkdirSync } from 'fs'; +import { dirname } from 'path'; + +import type { Bot } from '../@types/index.js'; + + +/** + * Manager for guild-specific language settings via SQLite + */ +export class GuildLanguageManager { + private db: Database.Database | null = null; + private bot: Bot; + private dbPath: string; + private guildLanguages: Map = new Map(); + private defaultLocale: string; + + constructor(bot: Bot) { + this.bot = bot; + this.dbPath = './data/guild_languages.db'; + this.defaultLocale = bot.config.bot.i18n.defaultLocale; + } + + /** + * Initialize the SQLite database and populate in-memory Map + */ + public initialize(): void { + try { + const dir = dirname(this.dbPath); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + this.db = new Database(this.dbPath); + + this.db.exec(` + CREATE TABLE IF NOT EXISTS guild_languages ( + guild_id TEXT PRIMARY KEY, + language TEXT NOT NULL + ) + `); + + // Populate in-memory Map + const rows = this.db.prepare('SELECT guild_id, language FROM guild_languages').all() as { guild_id: string, language: string }[]; + for (const row of rows) { + this.guildLanguages.set(row.guild_id, row.language); + } + + this.bot.logger.log( this.bot.shardId, `[GuildLanguageManager] Initialized with ${this.guildLanguages.size} guild-specific language(s)`); + } catch (error) { + this.bot.logger.error( this.bot.shardId, `[GuildLanguageManager] Failed to initialize: ${error}`); + } + } + + /** + * Get the language for a guild + */ + public get(guildId: string): string { + return this.guildLanguages.get(guildId) || this.defaultLocale; + } + + /** + * Set the language for a guild + */ + public set(guildId: string, language: string): void { + try { + if (this.db) { + this.db.prepare('INSERT OR REPLACE INTO guild_languages (guild_id, language) VALUES (?, ?)').run(guildId, language); + } + this.guildLanguages.set(guildId, language); + } catch (error) { + this.bot.logger.error( this.bot.shardId, `[GuildLanguageManager] Failed to set language for guild ${guildId}: ${error}`); + } + } + + /** + * Close the database connection + */ + public close(): void { + if (this.db) { + this.db.close(); + this.bot.logger.log( this.bot.shardId, '[GuildLanguageManager] Database connection closed.'); + } + } +} diff --git a/src/lib/builders/ButtonsBuilder.ts b/src/lib/builders/ButtonsBuilder.ts index 90aa4f6..1875b91 100644 --- a/src/lib/builders/ButtonsBuilder.ts +++ b/src/lib/builders/ButtonsBuilder.ts @@ -16,7 +16,7 @@ export class ButtonsBuilder { * @param {Player} player - The lavashark player instance * @returns {ActionRowBuilder} ActionRow containing all dashboard buttons */ - public static createDashboardButtons(player: Player): ActionRowBuilder { + public static createDashboardButtons(player: Player, _lng?: string): ActionRowBuilder { const playing = !player.paused; const playPauseButton = new ButtonBuilder() @@ -53,7 +53,7 @@ export class ButtonsBuilder { * @static * @returns {ActionRowBuilder} ActionRow containing queue navigation buttons */ - public static createQueueButtons(): ActionRowBuilder { + public static createQueueButtons(lng?: string): ActionRowBuilder { const prevButton = new ButtonBuilder() .setCustomId(QueueButtonId.Previous) .setEmoji(cst.button.emoji.prev) @@ -66,12 +66,12 @@ export class ButtonsBuilder { const delButton = new ButtonBuilder() .setCustomId(QueueButtonId.Delete) - .setLabel(i18next.t(cst.button.label.delete)) + .setLabel(i18next.t(cst.button.label.delete, { lng })) .setStyle(ButtonStyle.Primary); const clsButton = new ButtonBuilder() .setCustomId(QueueButtonId.Clear) - .setLabel(i18next.t(cst.button.label.clear)) + .setLabel(i18next.t(cst.button.label.clear, { lng })) .setStyle(ButtonStyle.Danger); return new ActionRowBuilder() diff --git a/src/lib/handlers/DashboardButtonHandler.ts b/src/lib/handlers/DashboardButtonHandler.ts index c96aa85..412ebd1 100644 --- a/src/lib/handlers/DashboardButtonHandler.ts +++ b/src/lib/handlers/DashboardButtonHandler.ts @@ -28,11 +28,13 @@ export abstract class DashboardButtonHandler { commandName: string, player: Player ): Promise { + const lng = bot.guildLanguageManager?.get(interaction.guildId!); + // Check admin permission if (bot.config.command.adminCommand.includes(commandName)) { if (!bot.config.bot.admin.includes(interaction.user.id)) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_REQUIRE_ADMIN'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_REQUIRE_ADMIN', { lng }))], flags: MessageFlags.Ephemeral }); return false; @@ -44,7 +46,7 @@ export abstract class DashboardButtonHandler { const member = interaction.member as GuildMember; if (!PermissionManager.hasDJCommandPermission(bot, interaction.user.id, member, player)) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_REQUIRE_DJ'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_REQUIRE_DJ', { lng }))], flags: MessageFlags.Ephemeral }); return false; diff --git a/src/lib/handlers/LoopButtonHandler.ts b/src/lib/handlers/LoopButtonHandler.ts index f98723b..6ca3789 100644 --- a/src/lib/handlers/LoopButtonHandler.ts +++ b/src/lib/handlers/LoopButtonHandler.ts @@ -27,25 +27,27 @@ export class LoopButtonHandler extends DashboardButtonHandler { return; } + const lng = bot.guildLanguageManager?.get(interaction.guildId!); + const modeLabels = [ - client.i18n.t('events:LOOP_MODE_OFF'), - client.i18n.t('events:LOOP_MODE_SINGLE'), - client.i18n.t('events:LOOP_MODE_ALL') + bot.i18n.t('events:LOOP_MODE_OFF', { lng }), + bot.i18n.t('events:LOOP_MODE_SINGLE', { lng }), + bot.i18n.t('events:LOOP_MODE_ALL', { lng }) ]; const select = new StringSelectMenuBuilder() .setCustomId(DashboardButtonId.LoopSelect) - .setPlaceholder(client.i18n.t('commands:LOOP_SELECT_PLACEHOLDER')) + .setPlaceholder(bot.i18n.t('commands:LOOP_SELECT_PLACEHOLDER', { lng })) .setOptions(this.LOOP_MODE_VALUES.map((value, index) => new StringSelectMenuOptionBuilder() .setLabel(modeLabels[index]) - .setDescription(client.i18n.t('commands:LOOP_SELECT_DESCRIPTION', { mode: modeLabels[index] })) + .setDescription(bot.i18n.t('commands:LOOP_SELECT_DESCRIPTION', { mode: modeLabels[index], lng })) .setValue(value) )); const row = new ActionRowBuilder().addComponents(select); const msg = await interaction.reply({ - embeds: [embeds.textMsg(bot, client.i18n.t('events:MESSAGE_SELECT_LOOP_MODE'))], + embeds: [embeds.textMsg(bot, bot.i18n.t('events:MESSAGE_SELECT_LOOP_MODE', { lng }))], flags: MessageFlags.Ephemeral, components: [row] }); @@ -78,11 +80,11 @@ export class LoopButtonHandler extends DashboardButtonHandler { player.setRepeatMode(mode); - const buttonRow = ButtonsBuilder.createDashboardButtons(player); + const buttonRow = ButtonsBuilder.createDashboardButtons(player, lng); await player.dashboardMsg?.edit({ components: [buttonRow] }); await i.update({ - embeds: [embeds.textSuccessMsg(bot, client.i18n.t('events:MESSAGE_SET_LOOP_MODE', { mode: modeLabel }))], + embeds: [embeds.textSuccessMsg(bot, bot.i18n.t('events:MESSAGE_SET_LOOP_MODE', { mode: modeLabel, lng }))], components: [] }); }); @@ -90,7 +92,7 @@ export class LoopButtonHandler extends DashboardButtonHandler { collector.on('end', async (collected: Collection, reason: string) => { if (reason === 'time' && collected.size === 0) { await msg.edit({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_TIME_EXPIRED'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('commands:ERROR_TIME_EXPIRED', { lng }))], components: [] }).catch(() => {}); } diff --git a/src/lib/handlers/MusicSaveButtonHandler.ts b/src/lib/handlers/MusicSaveButtonHandler.ts index 61f85a9..26c79c5 100644 --- a/src/lib/handlers/MusicSaveButtonHandler.ts +++ b/src/lib/handlers/MusicSaveButtonHandler.ts @@ -18,23 +18,25 @@ export class MusicSaveButtonHandler { const track = player.current; if (!track) return; + const lng = bot.guildLanguageManager?.get(interaction.guildId!); const member = interaction.member as GuildMember; - const subtitle = client.i18n.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { + const subtitle = bot.i18n.t('events:MESSAGE_NOW_PLAYING_SUBTITLE', { author: track.author, - label: track.duration.label + label: track.duration.label, + lng }); member.user.send({ embeds: [embeds.save(bot, track.title, subtitle, track.uri, track.thumbnail!)] }) .then(() => { interaction.reply({ - embeds: [embeds.textSuccessMsg(bot, client.i18n.t('events:MESSAGE_SEND_PRIVATE_MESSAGE'))], + embeds: [embeds.textSuccessMsg(bot, bot.i18n.t('events:MESSAGE_SEND_PRIVATE_MESSAGE', { lng }))], flags: MessageFlags.Ephemeral }).catch(() => { }); }) .catch((error) => { bot.logger.error( bot.shardId, '[MusicSaveButtonHandler] Error sending DM: ' + error); interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_SEND_PRIVATE_MESSAGE'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_SEND_PRIVATE_MESSAGE', { lng }))], flags: MessageFlags.Ephemeral }).catch(() => { }); }); diff --git a/src/lib/handlers/PlayPauseButtonHandler.ts b/src/lib/handlers/PlayPauseButtonHandler.ts index 0361fc1..8dd044a 100644 --- a/src/lib/handlers/PlayPauseButtonHandler.ts +++ b/src/lib/handlers/PlayPauseButtonHandler.ts @@ -20,6 +20,7 @@ export class PlayPauseButtonHandler extends DashboardButtonHandler { player: Player ): Promise { const playing = !player.paused; + const lng = bot.guildLanguageManager?.get(interaction.guildId!); // Check permission for the relevant command const commandToCheck = playing ? 'pause' : 'resume'; @@ -38,7 +39,7 @@ export class PlayPauseButtonHandler extends DashboardButtonHandler { const canDJBypass = bot.config.command.requesterDjBypass.includes('pause') && isDJ; if (!isRequester && !isAdmin && !canDJBypass) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_PAUSE_NOT_REQUESTER'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('commands:ERROR_PAUSE_NOT_REQUESTER', { lng }))], flags: MessageFlags.Ephemeral }); return; @@ -51,7 +52,7 @@ export class PlayPauseButtonHandler extends DashboardButtonHandler { await player.resume(); } - const row = ButtonsBuilder.createDashboardButtons(player); + const row = ButtonsBuilder.createDashboardButtons(player, lng); await interaction.update({ components: [row] }); } } diff --git a/src/lib/handlers/QueueButtonHandler.ts b/src/lib/handlers/QueueButtonHandler.ts index 5259d99..98e9b03 100644 --- a/src/lib/handlers/QueueButtonHandler.ts +++ b/src/lib/handlers/QueueButtonHandler.ts @@ -13,11 +13,11 @@ import type { Bot } from '../../@types/index.js'; */ export class QueueButtonHandler { private static readonly QUEUE_PAGE_SIZE = 5; - private static getLoopModeLabel(client: Client, repeatMode: number): string { + private static getLoopModeLabel(bot: Bot, repeatMode: number, lng?: string): string { const modes = [ - client.i18n.t('commands:REPEAT_MODE_OFF'), - client.i18n.t('commands:REPEAT_MODE_SINGLE'), - client.i18n.t('commands:REPEAT_MODE_ALL') + bot.i18n.t('commands:REPEAT_MODE_OFF', { lng }), + bot.i18n.t('commands:REPEAT_MODE_SINGLE', { lng }), + bot.i18n.t('commands:REPEAT_MODE_ALL', { lng }) ]; return modes[repeatMode] || modes[0]; } @@ -39,7 +39,8 @@ export class QueueButtonHandler { player.setting.queuePage.curPage--; } - await this.updateQueueDisplay(bot, client, player); + const lng = bot.guildLanguageManager?.get(interaction.guildId!); + await this.updateQueueDisplay(bot, client, player, lng); await interaction.deferUpdate(); } @@ -60,7 +61,8 @@ export class QueueButtonHandler { player.setting.queuePage.curPage++; } - await this.updateQueueDisplay(bot, client, player); + const lng = bot.guildLanguageManager?.get(interaction.guildId!); + await this.updateQueueDisplay(bot, client, player, lng); await interaction.deferUpdate(); } @@ -68,8 +70,8 @@ export class QueueButtonHandler { * Handle queue delete message button */ public static async handleDelete( - bot: Bot, - client: Client, + _bot: Bot, + _client: Client, interaction: ButtonInteraction, player: Player ): Promise { @@ -90,11 +92,13 @@ export class QueueButtonHandler { interaction: ButtonInteraction, player: Player ): Promise { + const lng = bot.guildLanguageManager?.get(interaction.guildId!); + // Check admin permission if (bot.config.command.adminCommand.includes('clear')) { if (!bot.config.bot.admin.includes(interaction.user.id)) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_REQUIRE_ADMIN'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_REQUIRE_ADMIN', { lng }))], flags: MessageFlags.Ephemeral }); return; @@ -106,7 +110,7 @@ export class QueueButtonHandler { const member = interaction.member as GuildMember; if (!PermissionManager.hasDJCommandPermission(bot, interaction.user.id, member, player)) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('events:ERROR_REQUIRE_DJ'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('events:ERROR_REQUIRE_DJ', { lng }))], flags: MessageFlags.Ephemeral }); return; @@ -124,7 +128,7 @@ export class QueueButtonHandler { player.setting.queuePage.maxPage = 1; } - await this.updateQueueDisplay(bot, client, player); + await this.updateQueueDisplay(bot, client, player, lng); await interaction.deferUpdate(); } @@ -135,8 +139,9 @@ export class QueueButtonHandler { */ private static async updateQueueDisplay( bot: Bot, - client: Client, - player: Player + _client: Client, + player: Player, + lng?: string ): Promise { if (!player.setting.queuePage) return; @@ -149,33 +154,33 @@ export class QueueButtonHandler { let maxTitleLength = 80; const buildDescription = (titleLength: number): string => { - const nowPlayingTitle = player.current?.title || client.i18n.t('commands:UNKNOWN_USER'); + const nowPlayingTitle = player.current?.title || bot.i18n.t('commands:UNKNOWN_USER', { lng }); const truncatedNP = nowPlayingTitle.length > titleLength ? nowPlayingTitle.substring(0, titleLength) + '...' : nowPlayingTitle; - let desc = `${client.i18n.t('embeds:QUEUE_NOW_PLAYING')}\n${truncatedNP}\n${'─'.repeat(20)}\n`; + let desc = `${bot.i18n.t('embeds:QUEUE_NOW_PLAYING', { lng })}\n${truncatedNP}\n${'─'.repeat(20)}\n`; const queueTracks = player.queue.tracks.slice(startIdx, endIdx); if (queueTracks.length < 1) { - desc += `\n*${client.i18n.t('embeds:QUEUE_EMPTY')}*`; + desc += `\n*${bot.i18n.t('embeds:QUEUE_EMPTY', { lng })}*`; } else { - desc += `\n${client.i18n.t('embeds:QUEUE_HEADER')}\n`; + desc += `\n${bot.i18n.t('embeds:QUEUE_HEADER', { lng })}\n`; const entries = queueTracks.map((track: any, index: number) => { let title = track.title; if (title.length > titleLength) { title = title.substring(0, titleLength) + '...'; } const requesterId = track.requester?.id; - const requesterMention = requesterId ? `<@${requesterId}>` : (track.requester?.username || client.i18n.t('commands:UNKNOWN_USER')); + const requesterMention = requesterId ? `<@${requesterId}>` : (track.requester?.username || bot.i18n.t('commands:UNKNOWN_USER', { lng })); return `${startIdx + index + 1}. ${title} **${track.duration.label}** | ${requesterMention}`; }); desc += entries.join('\n'); } if (totalTracks > 0 && maxPage > 1) { - desc += `\n\n${client.i18n.t('events:MESSAGE_QUEUE_PAGE', { curPage: page, maxPage })}`; + desc += `\n\n${bot.i18n.t('events:MESSAGE_QUEUE_PAGE', { curPage: page, maxPage, lng })}`; } return desc; @@ -193,10 +198,10 @@ export class QueueButtonHandler { } const repeatMode = player.repeatMode; - const row = ButtonsBuilder.createQueueButtons(); + const row = ButtonsBuilder.createQueueButtons(lng); await player.setting.queuePage.msg?.edit({ - embeds: [embeds.queue(bot, description, this.getLoopModeLabel(client, repeatMode))], + embeds: [embeds.queue(bot, description, this.getLoopModeLabel(bot, repeatMode, lng), lng)], components: [row], allowedMentions: { repliedUser: false } }); diff --git a/src/lib/handlers/ShuffleButtonHandler.ts b/src/lib/handlers/ShuffleButtonHandler.ts index 1cb92dc..ae5db6f 100644 --- a/src/lib/handlers/ShuffleButtonHandler.ts +++ b/src/lib/handlers/ShuffleButtonHandler.ts @@ -13,23 +13,24 @@ import type { Bot } from '../../@types/index.js'; export class ShuffleButtonHandler extends DashboardButtonHandler { public static async handle( bot: Bot, - client: Client, + _client: Client, interaction: ButtonInteraction, player: Player ): Promise { // Check shuffle permission - if (!await this.checkPermission(bot, client, interaction, 'shuffle', player)) { + if (!await this.checkPermission(bot, _client, interaction, 'shuffle', player)) { return; } + const lng = bot.guildLanguageManager?.get(interaction.guildId!); player.queue.shuffle(); - if (bot.config.queuePersistence.enabled && (client as any).queuePersistence) { - await (client as any).queuePersistence.saveQueue(player); + if (bot.config.queuePersistence.enabled && (_client as any).queuePersistence) { + await (_client as any).queuePersistence.saveQueue(player); } await interaction.reply({ - embeds: [embeds.textSuccessMsg(bot, client.i18n.t('events:MESSAGE_MUSIC_SHUFFLE'))], + embeds: [embeds.textSuccessMsg(bot, bot.i18n.t('events:MESSAGE_MUSIC_SHUFFLE', { lng }))], flags: MessageFlags.Ephemeral, components: [] }); diff --git a/src/lib/handlers/SkipButtonHandler.ts b/src/lib/handlers/SkipButtonHandler.ts index 3f09f20..02b5331 100644 --- a/src/lib/handlers/SkipButtonHandler.ts +++ b/src/lib/handlers/SkipButtonHandler.ts @@ -25,6 +25,8 @@ export class SkipButtonHandler extends DashboardButtonHandler { return; } + const lng = bot.guildLanguageManager?.get(interaction.guildId!); + // Check if skip is restricted to requester only if (bot.config.command.requesterOnly.includes('skip')) { const currentTrack = player.current; @@ -44,7 +46,7 @@ export class SkipButtonHandler extends DashboardButtonHandler { // Deny skip if user is not requester and doesn't have bypass permissions if (!isRequester && !isAdmin && !canDJBypass) { await interaction.reply({ - embeds: [embeds.textErrorMsg(bot, client.i18n.t('commands:ERROR_SKIP_NOT_REQUESTER'))], + embeds: [embeds.textErrorMsg(bot, bot.i18n.t('commands:ERROR_SKIP_NOT_REQUESTER', { lng }))], flags: MessageFlags.Ephemeral }); return; @@ -61,7 +63,7 @@ export class SkipButtonHandler extends DashboardButtonHandler { await player.skip(); } - const row = ButtonsBuilder.createDashboardButtons(player); + const row = ButtonsBuilder.createDashboardButtons(player, lng); await interaction.update({ components: [row] }); } }