diff --git a/functions/package-lock.json b/functions/package-lock.json index ee1415da..4071711e 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -1,28 +1,28 @@ { "name": "functions", - "version": "2.5.0", + "version": "2.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "functions", - "version": "2.5.0", + "version": "2.5.1", "dependencies": { - "@google-cloud/translate": "^9.0.1", - "compressing": "^1.10.1", + "@google-cloud/translate": "^9.1.0", + "compressing": "^1.10.3", "cors": "^2.8.5", "deepl-node": "^1.18.0", - "exiftool-vendored": "^29.3.0", + "exiftool-vendored": "^30.0.0", "express": "^5.1.0", - "firebase-admin": "^13.3.0", + "firebase-admin": "^13.4.0", "firebase-functions": "^6.3.2", "fluent-ffmpeg": "^2.1.3", - "sharp": "^0.34.1", + "sharp": "^0.34.2", "uuid": "^11.1.0", - "zod": "^3.24.4" + "zod": "^3.25.30" }, "devDependencies": { - "@types/express": "^5.0.1", + "@types/express": "^5.0.2", "@types/fluent-ffmpeg": "^2.1.27", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.9.0", @@ -484,9 +484,9 @@ } }, "node_modules/@google-cloud/translate": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/translate/-/translate-9.0.1.tgz", - "integrity": "sha512-NuIPXoSfdKBZPuWAIQPHCIMin0wlZPRcFxrhbKtByNNxFUjk3p2VKplw2C3O20/nWexhtFdkN8EwL58n+Lh5gw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/translate/-/translate-9.1.0.tgz", + "integrity": "sha512-W46KPgZydSrPKbFE2HhPrkQwxgNOtivaSBbOIYeC55LdnkcxCo/VZ143UegT6y6LXFAT51t1HK8RUrYEv7gNOQ==", "license": "Apache-2.0", "dependencies": { "@google-cloud/common": "^6.0.0", @@ -778,9 +778,9 @@ "license": "BSD-3-Clause" }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz", - "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz", + "integrity": "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==", "cpu": [ "arm64" ], @@ -800,9 +800,9 @@ } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz", - "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz", + "integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==", "cpu": [ "x64" ], @@ -966,9 +966,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz", - "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz", + "integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==", "cpu": [ "arm" ], @@ -988,9 +988,9 @@ } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz", - "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz", + "integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==", "cpu": [ "arm64" ], @@ -1010,9 +1010,9 @@ } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz", - "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz", + "integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==", "cpu": [ "s390x" ], @@ -1032,9 +1032,9 @@ } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz", - "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz", + "integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==", "cpu": [ "x64" ], @@ -1054,9 +1054,9 @@ } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz", - "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz", + "integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==", "cpu": [ "arm64" ], @@ -1076,9 +1076,9 @@ } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz", - "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz", + "integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==", "cpu": [ "x64" ], @@ -1098,16 +1098,16 @@ } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz", - "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz", + "integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==", "cpu": [ "wasm32" ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.4.0" + "@emnapi/runtime": "^1.4.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -1116,10 +1116,29 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz", + "integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz", - "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz", + "integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==", "cpu": [ "ia32" ], @@ -1136,9 +1155,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz", - "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz", + "integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==", "cpu": [ "x64" ], @@ -1333,9 +1352,9 @@ } }, "node_modules/@types/express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", - "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.2.tgz", + "integrity": "sha512-BtjL3ZwbCQriyb0DGw+Rt12qAXPiBTPs815lsUvtt1Grk0vLRMZNMUZ741d5rjk+UQOxfDiBZ3dxpX00vSkK3g==", "dev": true, "license": "MIT", "dependencies": { @@ -1397,9 +1416,9 @@ "optional": true }, "node_modules/@types/luxon": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.0.tgz", - "integrity": "sha512-RtEj20xRyG7cRp142MkQpV3GRF8Wo2MtDkKLz65MQs7rM1Lh8bz+HtfPXCCJEYpnDFu6VwAq/Iv2Ikyp9Jw/hw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz", + "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==", "license": "MIT" }, "node_modules/@types/mime": { @@ -2356,9 +2375,9 @@ } }, "node_modules/compressing": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/compressing/-/compressing-1.10.1.tgz", - "integrity": "sha512-XXwUffcVjqv8NGSQu1ttp6eMmuZ3zZEAec28Rt30o/vkXE20jXhowRQ9LXLY4uOgFkxXrNzApLobpam53Dc1AA==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/compressing/-/compressing-1.10.3.tgz", + "integrity": "sha512-F3RxWLU4UNfNYFVNwCK58HwQnv/5drvUW176FC//3i0pwpdahoZxMM7dkxWuA2MEafqfwDc+iudk70Sx/VMUIw==", "license": "MIT", "dependencies": { "@eggjs/yauzl": "^2.11.0", @@ -2647,9 +2666,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3256,26 +3275,26 @@ } }, "node_modules/exiftool-vendored": { - "version": "29.3.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-29.3.0.tgz", - "integrity": "sha512-2N+QvQH3mH0yb89vpxJXaD+SXa8GXvDigytS6cro6FOrTx9Opav4H0QPP0V4r9dBhXy5poON7qo+p1KZv5wZqQ==", + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-30.0.0.tgz", + "integrity": "sha512-fSNjsIMjh/ssRP2GEScqGp7uTHhaJWxfDQPhWAyvckSpI/0DnU3glpzrF1VzGrc5Fi4ZMBcMj7N4AC1QoWHZNA==", "license": "MIT", "dependencies": { "@photostructure/tz-lookup": "^11.2.0", - "@types/luxon": "^3.6.0", + "@types/luxon": "^3.6.2", "batch-cluster": "^13.0.0", "he": "^1.2.0", "luxon": "^3.6.1" }, "optionalDependencies": { - "exiftool-vendored.exe": "13.26.0", - "exiftool-vendored.pl": "13.26.0" + "exiftool-vendored.exe": "13.29.0", + "exiftool-vendored.pl": "13.29.0" } }, "node_modules/exiftool-vendored.exe": { - "version": "13.26.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-13.26.0.tgz", - "integrity": "sha512-y5mLmNAeABbXhb1a77EeR+CYDELI64DawnNywhMiivIYIdBPXf/81UcBd3SQlcTAHJjJjKt20qdmdL4loMkRDA==", + "version": "13.29.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-13.29.0.tgz", + "integrity": "sha512-n82fD6uhoEzCwhJl2RbJFaAD6f5/8sP4Z6+rxYh1MVLrccUK7sj3cgg1e34BPbcD9v2dGyX29slTGj6iLmMZFw==", "license": "MIT", "optional": true, "os": [ @@ -3283,9 +3302,9 @@ ] }, "node_modules/exiftool-vendored.pl": { - "version": "13.26.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-13.26.0.tgz", - "integrity": "sha512-4L8b6TrZcrd/dOnoeyCgsIa4WgFygucd0KQzB7xgWpgeMDQ2xYeqAYoHTeKmLAv4DogvaVkZQgDNogscuKuM+Q==", + "version": "13.29.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-13.29.0.tgz", + "integrity": "sha512-NijMl890wa+1i5ytBaMF0OV4pEZw/n6cB+cu8aTFG5SgrHUl88C4rC7b3tsfO/g1qvMDftG693m91usVoZxK0A==", "license": "MIT", "optional": true, "os": [ @@ -3562,9 +3581,9 @@ } }, "node_modules/firebase-admin": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.3.0.tgz", - "integrity": "sha512-MFxv86Aw2rjM/TpKwU86jN7YUFfN1jy6mREYZTLL1aW1rCpZFi4c70b9U12J9Xa4RbJkiXpWBAwth9IVSqR91A==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.4.0.tgz", + "integrity": "sha512-Y8DcyKK+4pl4B93ooiy1G8qvdyRMkcNFfBSh+8rbVcw4cW8dgG0VXCCTp5NUwub8sn9vSPsOwpb9tE2OuFmcfQ==", "license": "Apache-2.0", "dependencies": { "@fastify/busboy": "^3.0.0", @@ -6243,9 +6262,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6353,15 +6372,15 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz", - "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==", + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz", + "integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.7.1" + "detect-libc": "^2.0.4", + "semver": "^7.7.2" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -6370,8 +6389,8 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.1", - "@img/sharp-darwin-x64": "0.34.1", + "@img/sharp-darwin-arm64": "0.34.2", + "@img/sharp-darwin-x64": "0.34.2", "@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-linux-arm": "1.1.0", @@ -6381,15 +6400,16 @@ "@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0", - "@img/sharp-linux-arm": "0.34.1", - "@img/sharp-linux-arm64": "0.34.1", - "@img/sharp-linux-s390x": "0.34.1", - "@img/sharp-linux-x64": "0.34.1", - "@img/sharp-linuxmusl-arm64": "0.34.1", - "@img/sharp-linuxmusl-x64": "0.34.1", - "@img/sharp-wasm32": "0.34.1", - "@img/sharp-win32-ia32": "0.34.1", - "@img/sharp-win32-x64": "0.34.1" + "@img/sharp-linux-arm": "0.34.2", + "@img/sharp-linux-arm64": "0.34.2", + "@img/sharp-linux-s390x": "0.34.2", + "@img/sharp-linux-x64": "0.34.2", + "@img/sharp-linuxmusl-arm64": "0.34.2", + "@img/sharp-linuxmusl-x64": "0.34.2", + "@img/sharp-wasm32": "0.34.2", + "@img/sharp-win32-arm64": "0.34.2", + "@img/sharp-win32-ia32": "0.34.2", + "@img/sharp-win32-x64": "0.34.2" } }, "node_modules/shebang-command": { @@ -7337,9 +7357,9 @@ } }, "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "version": "3.25.30", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.30.tgz", + "integrity": "sha512-VolhdEtu6TJr/fzGuHA/SZ5ixvXqA6ADOG9VRcQ3rdOKmF5hkmcJbyaQjUH5BgmpA9gej++zYRX7zjSmdReIwA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/functions/package.json b/functions/package.json index 2bf0cf2e..ceb7493c 100644 --- a/functions/package.json +++ b/functions/package.json @@ -1,6 +1,6 @@ { "name": "functions", - "version": "2.5.0", + "version": "2.5.1", "scripts": { "lint": "eslint --ext .js,.ts .", "lint:fix": "eslint --fix --ext .js,.ts .", @@ -19,21 +19,21 @@ }, "main": "lib/index.js", "dependencies": { - "@google-cloud/translate": "^9.0.1", - "compressing": "^1.10.1", + "@google-cloud/translate": "^9.1.0", + "compressing": "^1.10.3", "cors": "^2.8.5", "deepl-node": "^1.18.0", - "exiftool-vendored": "^29.3.0", + "exiftool-vendored": "^30.0.0", "express": "^5.1.0", - "firebase-admin": "^13.3.0", + "firebase-admin": "^13.4.0", "firebase-functions": "^6.3.2", "fluent-ffmpeg": "^2.1.3", - "sharp": "^0.34.1", + "sharp": "^0.34.2", "uuid": "^11.1.0", - "zod": "^3.24.4" + "zod": "^3.25.30" }, "devDependencies": { - "@types/express": "^5.0.1", + "@types/express": "^5.0.2", "@types/fluent-ffmpeg": "^2.1.27", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.9.0", diff --git a/functions/src/services/open-api.service.ts b/functions/src/services/open-api.service.ts index d6e1d6be..d93ba29e 100644 --- a/functions/src/services/open-api.service.ts +++ b/functions/src/services/open-api.service.ts @@ -311,7 +311,7 @@ export function generateOpenApi(schemasById: Map): OpenAPIObject openapi: '3.0.3', info: { title: 'Localess Open API Specification', - version: '2.5.0', + version: '2.5.1', description: 'Fetch data from Localess via REST API', contact: { name: 'Lessify Team', diff --git a/package-lock.json b/package-lock.json index 0546990a..83d96e03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "localess", - "version": "2.5.0", + "version": "2.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "localess", - "version": "2.5.0", + "version": "2.5.1", "license": "MIT", "dependencies": { "@angular/animations": "^19.2.2", @@ -20,9 +20,9 @@ "@angular/platform-browser": "^19.2.2", "@angular/platform-browser-dynamic": "^19.2.2", "@angular/router": "^19.2.2", - "@ngrx/operators": "^19.1.0", - "@ngrx/signals": "^19.1.0", - "@ngrx/store-devtools": "^19.1.0", + "@ngrx/operators": "^19.2.0", + "@ngrx/signals": "^19.2.0", + "@ngrx/store-devtools": "^19.2.0", "@stoplight/elements": "^9.0.1", "@tiptap/core": "^2.7.1", "@tiptap/extension-bold": "^2.7.1", @@ -84,7 +84,7 @@ "prettier": "^3.5.3", "prettier-eslint": "^16.4.2", "tailwindcss": "^3.4.17", - "tsup": "^8.4.0", + "tsup": "^8.5.0", "typescript": "~5.8.2", "webpack-bundle-analyzer": "^4.10.2" }, @@ -5789,9 +5789,9 @@ } }, "node_modules/@ngrx/operators": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-19.1.0.tgz", - "integrity": "sha512-gpo69FnoAF69X68pk9eWFHB630xqerBYkF68wOFMciLOV2im3b/fAf+0sRvnQJmVtG/8jO0IPVdvLqa1TbWmPA==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-19.2.0.tgz", + "integrity": "sha512-kv3hFlpWbZxfyILvQAJT2JNbsRGauUIj67U6zOUd8psD7qoJdtdUAZmr/LUgu/6/tweYDUj1mcQJfvaudik0ZQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -5801,9 +5801,9 @@ } }, "node_modules/@ngrx/signals": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@ngrx/signals/-/signals-19.1.0.tgz", - "integrity": "sha512-v8sbb+Iox9kdIaKbFgt4Z1W+NxzIU4+g+6qQU6/c27UmtQXv0s1zUKKofPRK0qwkaZzNWkxNToxyoE285ukqcQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/signals/-/signals-19.2.0.tgz", + "integrity": "sha512-7NMFZE5HU/bryU15vCA2aHDYa77Pb7nCit4UOcsk9WkCSWMwtyQ4/9eR6wSGdeX5xxDcFQy0IiZJ8wxYCxA5Eg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -5819,9 +5819,9 @@ } }, "node_modules/@ngrx/store": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-19.1.0.tgz", - "integrity": "sha512-8kKCSFahTpRTx3f/wwcDjItdFnk2IMoorWRjTI2U/MGWuEi4flqLNWcX99s759e7TI6PctiGsaS8jnJXIUS8Jg==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-19.2.0.tgz", + "integrity": "sha512-k2n/jLJZ75Z5rd5vPa2mXPYG/On2rFLiNdrccs9Dw2r+oJosORMlN5TbdsGHhVDFfjzbY9a7JbHUE3YOa69gqw==", "license": "MIT", "peer": true, "dependencies": { @@ -5833,16 +5833,16 @@ } }, "node_modules/@ngrx/store-devtools": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-19.1.0.tgz", - "integrity": "sha512-c8sV5FQofqm7lF6HTJ4Bb7L/69TaIYHAwEFMbgqbsNoWDx+pilw/It6X9J3LnU8bXjQK4xg6qsgJ7fJIH5X2NA==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-19.2.0.tgz", + "integrity": "sha512-AKlXHsuSRJgYYxmrXZ8WWnDxqgKMG0+HP+IIDmk5h5Z5RIkOLHk6ZGKbakhIiFlL8d16N+GcJ76rqUajaHT+0w==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@angular/core": "^19.0.0", - "@ngrx/store": "19.1.0", + "@ngrx/store": "19.2.0", "rxjs": "^6.5.3 || ^7.5.0" } }, @@ -13569,6 +13569,18 @@ "@firebase/vertexai": "1.2.2" } }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -17765,8 +17777,8 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", @@ -17778,15 +17790,15 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/mlly/node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", @@ -19053,8 +19065,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", @@ -23299,9 +23311,9 @@ "license": "0BSD" }, "node_modules/tsup": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.4.0.tgz", - "integrity": "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", + "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -23311,6 +23323,7 @@ "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", + "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", @@ -23596,8 +23609,8 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/uglify-js": { "version": "3.19.3", diff --git a/package.json b/package.json index 84482309..ab916642 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "localess", - "version": "2.5.0", + "version": "2.5.1", "engines": { "node": "20" }, @@ -39,9 +39,9 @@ "@angular/platform-browser": "^19.2.2", "@angular/platform-browser-dynamic": "^19.2.2", "@angular/router": "^19.2.2", - "@ngrx/operators": "^19.1.0", - "@ngrx/signals": "^19.1.0", - "@ngrx/store-devtools": "^19.1.0", + "@ngrx/operators": "^19.2.0", + "@ngrx/signals": "^19.2.0", + "@ngrx/store-devtools": "^19.2.0", "@stoplight/elements": "^9.0.1", "@tiptap/core": "^2.7.1", "@tiptap/extension-bold": "^2.7.1", @@ -103,7 +103,7 @@ "prettier": "^3.5.3", "prettier-eslint": "^16.4.2", "tailwindcss": "^3.4.17", - "tsup": "^8.4.0", + "tsup": "^8.5.0", "typescript": "~5.8.2", "webpack-bundle-analyzer": "^4.10.2" } diff --git a/src/app/features/admin/spaces/spaces.component.html b/src/app/features/admin/spaces/spaces.component.html index 3794f6bb..3d145829 100644 --- a/src/app/features/admin/spaces/spaces.component.html +++ b/src/app/features/admin/spaces/spaces.component.html @@ -18,7 +18,7 @@ ID - {{ element.id }} + {{ element.id }} diff --git a/src/app/features/spaces/settings/tokens/tokens.component.html b/src/app/features/spaces/settings/tokens/tokens.component.html index 3b58fe55..5989cc05 100644 --- a/src/app/features/spaces/settings/tokens/tokens.component.html +++ b/src/app/features/spaces/settings/tokens/tokens.component.html @@ -18,7 +18,7 @@ Id - {{ element.id }} + {{ element.id.substring(0, 10) }}... diff --git a/src/app/features/spaces/translations/add-dialog/add-dialog.component.ts b/src/app/features/spaces/translations/add-dialog/add-dialog.component.ts index ed675f88..2ddf5efc 100644 --- a/src/app/features/spaces/translations/add-dialog/add-dialog.component.ts +++ b/src/app/features/spaces/translations/add-dialog/add-dialog.component.ts @@ -49,9 +49,7 @@ export class AddDialogComponent { private readonly fb: FormBuilder, readonly fe: FormErrorHandlerService, @Inject(MAT_DIALOG_DATA) public data: AddDialogModel, - ) { - this.form.valueChanges.subscribe(it => console.log(it)); - } + ) {} addLabel(event: MatChipInputEvent): void { const input = event.chipInput?.inputElement; diff --git a/src/app/features/spaces/translations/shared/models/translation.model.ts b/src/app/features/spaces/translations/shared/models/translation.model.ts new file mode 100644 index 00000000..d5c349b5 --- /dev/null +++ b/src/app/features/spaces/translations/shared/models/translation.model.ts @@ -0,0 +1,5 @@ +export interface TranslationNode { + name: string; + key: string; + children?: TranslationNode[]; +} diff --git a/src/app/features/spaces/translations/translations.component.html b/src/app/features/spaces/translations/translations.component.html index 32f8ea99..2d522ac2 100644 --- a/src/app/features/spaces/translations/translations.component.html +++ b/src/app/features/spaces/translations/translations.component.html @@ -7,6 +7,17 @@
+ + + view_list + + + account_tree + + @if ('TRANSLATION_CREATE' | canUserPerform | async) { + {{ node.name }} + + +
+ } +
+ @if (selectedTranslation) { +
+
+ + Source Locale + + @for (locale of space.locales; track locale.id) { + {{ locale.name }} + } + + +
+ +
+ @switch (selectedTranslation.type) { + @case ('STRING') { + + } + @case ('PLURAL') { + + } + @case ('ARRAY') { + + } + } +
+ + @if ('TRANSLATION_UPDATE' | canUserPerform | async) { +
- - Source Locale - + + Target Locale + @for (locale of space.locales; track locale.id) { {{ locale.name }} } +
-
+
@switch (selectedTranslation.type) { @case ('STRING') { - + + + + } @case ('PLURAL') { - + } @case ('ARRAY') { - + } }
- + +
+ +
+ } +
+ } +
+
+ @if (selectedTranslation) { +
+
@if ('TRANSLATION_UPDATE' | canUserPerform | async) { - -
- - Target Locale - - @for (locale of space.locales; track locale.id) { - {{ locale.name }} - } - - - + + + } + @if ('TRANSLATION_DELETE' | canUserPerform | async) { + + } +
+ +
    +
  • +
    +
    +
    ID
    +
    +
    + {{ selectedTranslation.id }} + +
    - -
    - @switch (selectedTranslation.type) { - @case ('STRING') { - - - - - } - @case ('PLURAL') { - - } - @case ('ARRAY') { - - } - } +
  • +
  • +
    +
    +
    Status
    +
    +
    + {{ identifyStatus(selectedTranslation) }} +
    - -
    - +
  • +
  • +
    +
    +
    Description
    +
    +
    + @if (selectedTranslation.description) { + {{ selectedTranslation.description }} + } @else { + No description available. + } +
    - } -
- } -
-
- @if (selectedTranslation) { -
-
- @if ('TRANSLATION_UPDATE' | canUserPerform | async) { - - - } - @if ('TRANSLATION_DELETE' | canUserPerform | async) { - - } -
- -
    -
  • -
    -
    -
    ID
    -
    -
    - {{ selectedTranslation.id }} - -
    +
  • +
  • +
    +
    +
    Labels
    -
  • -
  • -
    -
    -
    Status
    -
    -
    - {{ identifyStatus(selectedTranslation) }} -
    +
    + @if (selectedTranslation.labels && selectedTranslation.labels.length > 0) { + + @for (label of selectedTranslation.labels; track label) { + {{ label }} + } + + } @else { + No label available. + }
    -
  • -
  • -
    -
    -
    Description
    -
    -
    - @if (selectedTranslation.description) { - {{ selectedTranslation.description }} - } @else { - No description available. - } -
    +
    +
  • +
  • +
    +
    +
    Created At
    -
  • -
  • -
    -
    -
    Labels
    -
    -
    - @if (selectedTranslation.labels && selectedTranslation.labels.length > 0) { - - @for (label of selectedTranslation.labels; track label) { - {{ label }} - } - - } @else { - No label available. - } -
    +
    + {{ selectedTranslation.createdAt?.toMillis() | date: 'medium' }}
    -
  • -
  • -
    -
    -
    Created At
    -
    -
    - {{ selectedTranslation.createdAt?.toMillis() | date: 'medium' }} -
    +
    +
  • +
  • +
    +
    +
    Updated At
    -
  • -
  • -
    -
    -
    Updated At
    -
    -
    - {{ selectedTranslation.updatedAt?.toMillis() | date: 'medium' }} -
    +
    + {{ selectedTranslation.updatedAt?.toMillis() | date: 'medium' }}
    -
  • -
  • -
    -
    -
    Updated By
    -
    -
    - {{ selectedTranslation.updatedBy?.name || 'Unknown' }} -
    +
    +
  • +
  • +
    +
    +
    Updated By
    -
  • -
-
- } -
+
+ {{ selectedTranslation.updatedBy?.name || 'Unknown' }} +
+
+ + +
+ } - } + diff --git a/src/app/features/spaces/translations/translations.component.scss b/src/app/features/spaces/translations/translations.component.scss index 0c1d020b..8fa431f0 100644 --- a/src/app/features/spaces/translations/translations.component.scss +++ b/src/app/features/spaces/translations/translations.component.scss @@ -33,8 +33,6 @@ mat-form-field.with-button { } .overlay { - //overflow: overlay; - //height: 87vh; height: calc(100vh - 216px); padding: inherit; border: 1px solid #efefef; diff --git a/src/app/features/spaces/translations/translations.component.ts b/src/app/features/spaces/translations/translations.component.ts index 40cbd3bb..f3050249 100644 --- a/src/app/features/spaces/translations/translations.component.ts +++ b/src/app/features/spaces/translations/translations.component.ts @@ -4,9 +4,10 @@ import { ScrollingModule } from '@angular/cdk/scrolling'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, DestroyRef, inject, input, OnInit, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { FormBuilder, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatChipsModule } from '@angular/material/chips'; import { MatDialog } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; @@ -19,6 +20,7 @@ import { MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTreeModule } from '@angular/material/tree'; import { ObjectUtils } from '@core/utils/object-utils.service'; import { ConfirmationDialogComponent } from '@shared/components/confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogModel } from '@shared/components/confirmation-dialog/confirmation-dialog.model'; @@ -29,7 +31,6 @@ import { Space } from '@shared/models/space.model'; import { TranslationHistory } from '@shared/models/translation-history.model'; import { Translation, TranslationCreate, TranslationStatus, TranslationUpdate } from '@shared/models/translation.model'; import { CanUserPerformPipe } from '@shared/pipes/can-user-perform.pipe'; -import { TranslationFilterPipe } from '@shared/pipes/translation-filter.pipe'; import { LocaleService } from '@shared/services/locale.service'; import { NotificationService } from '@shared/services/notification.service'; import { SpaceService } from '@shared/services/space.service'; @@ -38,7 +39,8 @@ import { TokenService } from '@shared/services/token.service'; import { TranslateService } from '@shared/services/translate.service'; import { TranslationHistoryService } from '@shared/services/translation-history.service'; import { TranslationService } from '@shared/services/translation.service'; -import { debounceTime, EMPTY, Observable } from 'rxjs'; +import { LocalSettingsStore } from '@shared/stores/local-settings.store'; +import { EMPTY, Observable } from 'rxjs'; import { filter, switchMap, tap } from 'rxjs/operators'; import { AddDialogComponent, AddDialogModel, AddDialogReturnModel } from './add-dialog'; import { EditDialogComponent, EditDialogModel } from './edit-dialog'; @@ -47,6 +49,7 @@ import { ExportDialogComponent } from './export-dialog/export-dialog.component'; import { ExportDialogModel, ExportDialogReturn } from './export-dialog/export-dialog.model'; import { ImportDialogComponent } from './import-dialog/import-dialog.component'; import { ImportDialogModel, ImportDialogReturn } from './import-dialog/import-dialog.model'; +import { TranslationNode } from './shared/models/translation.model'; import { TranslationArrayEditComponent } from './translation-array-edit/translation-array-edit.component'; import { TranslationArrayViewComponent } from './translation-array-view/translation-array-view.component'; import { TranslationPluralEditComponent } from './translation-plural-edit/translation-plural-edit.component'; @@ -78,7 +81,6 @@ import { TranslationStringViewComponent } from './translation-string-view/transl FormsModule, MatAutocompleteModule, ScrollingModule, - TranslationFilterPipe, StatusComponent, TranslationStringViewComponent, TranslationPluralViewComponent, @@ -88,6 +90,8 @@ import { TranslationStringViewComponent } from './translation-string-view/transl TranslationArrayEditComponent, ClipboardModule, AnimateDirective, + MatButtonToggleModule, + MatTreeModule, ], }) export class TranslationsComponent implements OnInit { @@ -100,14 +104,17 @@ export class TranslationsComponent implements OnInit { DEFAULT_LOCALE = 'en'; translations = signal([]); + translationsFiltered = computed(() => + this.filterTranslations(this.translations(), this.searchValue(), this.selectedSearchLocale(), this.filterLabels()), + ); + translationTreeFiltered = computed(() => this.buildTranslationTree(this.translationsFiltered())); translationIds = computed(() => this.translations().map(it => it.id)); - + translationMap = computed(() => new Map(this.translations().map(it => [it.id, it]))); //Search - searchCtrl: FormControl = this.fb.control(''); - searchValue = ''; + searchValue = signal(''); //Labels - currentLabel = ''; + currentLabel = signal(''); readonly separatorKeysCodes = [ENTER, COMMA, SPACE] as const; allLabels = computed(() => { const tmp = this.translations() @@ -119,7 +126,7 @@ export class TranslationsComponent implements OnInit { }); filterLabels = signal([]); filteredLabels = computed(() => { - const currentLabel = this.currentLabel.toLowerCase(); + const currentLabel = this.currentLabel().toLowerCase(); if (currentLabel) { return this.allLabels() .filter(label => !this.filterLabels().includes(label)) @@ -131,16 +138,15 @@ export class TranslationsComponent implements OnInit { selectedTranslation?: Translation; selectedTranslationLocaleValue?: string; - selectedSearchLocale = ''; - selectedSourceLocale = ''; - selectedTargetLocale = ''; + selectedSearchLocale = signal(''); + selectedSourceLocale = signal(''); + selectedTargetLocale = signal(''); availableToken?: string = undefined; // Subscriptions history$?: Observable; space$?: Observable; - translations$?: Observable; //Loadings isLoading = signal(true); @@ -148,7 +154,11 @@ export class TranslationsComponent implements OnInit { isLocaleUpdateLoading = signal(false); isTranslateLoading = signal(false); + translationUpdateId = signal(undefined); + private destroyRef = inject(DestroyRef); + // Local Settings + settingsStore = inject(LocalSettingsStore); constructor( private readonly translationService: TranslationService, @@ -164,48 +174,50 @@ export class TranslationsComponent implements OnInit { private readonly fb: FormBuilder, ) {} + // Tree features + childrenAccessor = (node: TranslationNode) => node.children ?? []; + hasChild = (_: number, node: TranslationNode) => !!node.children && node.children.length > 0; + trackBy = (_: number, node: TranslationNode) => this.expansionKey(node); + expansionKey = (node: TranslationNode) => node.key; + ngOnInit(): void { - this.searchCtrl.valueChanges.pipe(debounceTime(300), takeUntilDestroyed(this.destroyRef)).subscribe({ - next: value => { - this.searchValue = value; - this.cd.markForCheck(); - }, - }); this.space$ = this.spaceService.findById(this.spaceId()).pipe( tap(space => { this.selectedSpace = space; //this.locales = space.locales; - if (this.selectedSearchLocale === '') { - this.selectedSearchLocale = space.localeFallback.id; + if (this.selectedSearchLocale() === '') { + this.selectedSearchLocale.set(space.localeFallback.id); } - if (this.selectedSourceLocale === '') { - this.selectedSourceLocale = space.localeFallback.id; + if (this.selectedSourceLocale() === '') { + this.selectedSourceLocale.set(space.localeFallback.id); } - if (this.selectedTargetLocale === '') { - this.selectedTargetLocale = space.localeFallback.id; + if (this.selectedTargetLocale() === '') { + this.selectedTargetLocale.set(space.localeFallback.id); } }), takeUntilDestroyed(this.destroyRef), ); - this.translations$ = this.translationService.findAll(this.spaceId()).pipe( - tap(translations => { - this.translations.set(translations); - if (translations.length > 0) { - if (this.selectedTranslation) { - const tr = translations.find(it => it.id === this.selectedTranslation?.id); - if (tr) { - this.selectTranslation(tr); + this.translationService + .findAll(this.spaceId()) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: translations => { + this.translations.set(translations); + if (translations.length > 0) { + if (this.selectedTranslation) { + const tr = translations.find(it => it.id === this.selectedTranslation?.id); + if (tr) { + this.selectTranslation(tr); + } else { + this.selectTranslation(translations[0]); + } } else { this.selectTranslation(translations[0]); } - } else { - this.selectTranslation(translations[0]); } - } - this.isLoading.set(false); - }), - takeUntilDestroyed(this.destroyRef), - ); + this.isLoading.set(false); + }, + }); this.history$ = this.translateHistoryService.findAll(this.spaceId()).pipe(takeUntilDestroyed(this.destroyRef)); } @@ -410,17 +422,61 @@ export class TranslationsComponent implements OnInit { }); } + filterTranslations(items: Translation[], filter: string, locale: string, labels: string[]): Translation[] { + const lcFilter = filter.toLowerCase(); + if (!items || (!filter && !labels.length)) { + return items; + } + return items.filter(it => { + const matchByLabel = !labels.length || (it.labels && it.labels.length > 0 && labels.every(label => it.labels?.includes(label))); + if (it.id.toLowerCase().indexOf(lcFilter) !== -1 && matchByLabel) { + return true; + } else { + if (it.locales[locale]) { + return it.locales[locale].indexOf(lcFilter) !== -1 && matchByLabel; + } + return false; + } + }); + } + + buildTranslationTree(translations: Translation[]): TranslationNode[] { + const tTree: Record = {}; + for (const translation of translations) { + const keys = translation.id.split('.'); + let currentNode = tTree; + // construct shape + for (const key of keys) { + if (!currentNode[key]) { + currentNode[key] = {}; + } + currentNode = currentNode[key]; + } + } + + function convertTree(node: Record, prefix?: string): TranslationNode[] { + return Object.entries(node).map(([key, value]) => ({ + name: key, + key: prefix ? `${prefix}.${key}` : key, + ...(Object.keys(value).length > 0 && { children: convertTree(value, prefix ? `${prefix}.${key}` : key) }), + })); + } + + return convertTree(tTree); + } + selectTranslation(translation: Translation): void { this.selectedTranslation = translation; - this.selectedTranslationLocaleValue = this.selectedTranslation.locales[this.selectedTargetLocale]; + this.selectedTranslationLocaleValue = this.selectedTranslation.locales[this.selectedTargetLocale()]; } selectTargetLocale(): void { - this.selectedTranslationLocaleValue = this.selectedTranslation?.locales[this.selectedTargetLocale]; + this.selectedTranslationLocaleValue = this.selectedTranslation?.locales[this.selectedTargetLocale()]; } updateLocale(transaction: Translation, locale: string, value: string): void { this.isLocaleUpdateLoading.set(true); + this.translationUpdateId.set(transaction.id); this.translationService.updateLocale(this.spaceId(), transaction.id, locale, value).subscribe({ next: () => { this.notificationService.success('Translation has been updated.'); @@ -431,6 +487,7 @@ export class TranslationsComponent implements OnInit { complete: () => { setTimeout(() => { this.isLocaleUpdateLoading.set(false); + this.translationUpdateId.set(undefined); this.cd.markForCheck(); }, 1000); }, @@ -441,7 +498,7 @@ export class TranslationsComponent implements OnInit { selectLabel(event: MatAutocompleteSelectedEvent): void { const { option } = event; this.filterLabels.update(it => [...it, option.viewValue]); - this.currentLabel = ''; + this.currentLabel.set(''); option.deselect(); this.selectedTranslation = undefined; } @@ -485,9 +542,9 @@ export class TranslationsComponent implements OnInit { this.isTranslateLoading.set(true); this.translateService .translate({ - content: this.selectedTranslation?.locales[this.selectedSourceLocale] || '', - sourceLocale: this.selectedSourceLocale, - targetLocale: this.selectedTargetLocale, + content: this.selectedTranslation?.locales[this.selectedSourceLocale()] || '', + sourceLocale: this.selectedSourceLocale(), + targetLocale: this.selectedTargetLocale(), }) .subscribe({ next: value => { diff --git a/src/app/shared/components/status/status.component.scss b/src/app/shared/components/status/status.component.scss index 4b374974..893a16a9 100644 --- a/src/app/shared/components/status/status.component.scss +++ b/src/app/shared/components/status/status.component.scss @@ -1,4 +1,5 @@ :host { display: flex; align-items: center; + @apply p-1 } diff --git a/src/app/shared/pipes/translation-filter.pipe.ts b/src/app/shared/pipes/translation-filter.pipe.ts deleted file mode 100644 index 1b237ebd..00000000 --- a/src/app/shared/pipes/translation-filter.pipe.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; -import { Translation } from '../models/translation.model'; - -@Pipe({ - name: 'translationFilter', - standalone: true, -}) -export class TranslationFilterPipe implements PipeTransform { - transform(items: Translation[], filter: string, locale: string, labels: string[]): Translation[] { - if (!items || (!filter && !labels.length)) { - return items; - } - return items.filter(it => { - const matchByLabel = !labels.length || (it.labels && it.labels.length > 0 && labels.every(label => it.labels?.includes(label))); - if (it.id.indexOf(filter) !== -1 && matchByLabel) { - return true; - } else { - if (it.locales[locale]) { - return it.locales[locale].indexOf(filter) !== -1 && matchByLabel; - } - return false; - } - }); - } -} diff --git a/src/app/shared/stores/local-settings.store.ts b/src/app/shared/stores/local-settings.store.ts index 6685964c..2946ddd6 100644 --- a/src/app/shared/stores/local-settings.store.ts +++ b/src/app/shared/stores/local-settings.store.ts @@ -6,6 +6,7 @@ const LS_KEY = 'LL-SETTINGS-STATE'; export type Theme = 'light' | 'dark' | 'auto'; export type EditorSize = '' | 'sm' | 'md' | 'lg'; export type DataLayout = 'list' | 'grid'; +export type TranslationLayout = 'list' | 'tree'; export interface LocalSettingsState { theme: Theme; @@ -16,6 +17,7 @@ export interface LocalSettingsState { editorFormWidth: number; assetLayout: DataLayout; assetDialogLayout: DataLayout; + translationLayout: TranslationLayout; } export const initialState: LocalSettingsState = { @@ -27,6 +29,7 @@ export const initialState: LocalSettingsState = { editorFormWidth: 700, assetLayout: 'list', assetDialogLayout: 'list', + translationLayout: 'list', }; function setDocumentTheme(theme: Theme) { @@ -102,6 +105,10 @@ export const LocalSettingsStore = signalStore( patchState(store, { assetDialogLayout }); localStorage.setItem(LS_KEY, JSON.stringify({ ...getState(store), assetDialogLayout })); }, + setTranslationLayout: (translationLayout: TranslationLayout): void => { + patchState(store, { translationLayout }); + localStorage.setItem(LS_KEY, JSON.stringify({ ...getState(store), translationLayout })); + }, }; }), withComputed(store => { @@ -114,6 +121,7 @@ export const LocalSettingsStore = signalStore( editorFormWidth: computed(() => store.editorFormWidth()), assetLayout: computed(() => store.assetLayout()), assetDialogLayout: computed(() => store.assetDialogLayout()), + translationLayout: computed(() => store.translationLayout()), }; }), withHooks({ diff --git a/src/environments/environment.docker.ts b/src/environments/environment.docker.ts index 8d3f3b25..564cd658 100644 --- a/src/environments/environment.docker.ts +++ b/src/environments/environment.docker.ts @@ -26,5 +26,5 @@ export const environment = { emulator : { enabled: true, }, - version: '2.5.0', + version: '2.5.1', }; diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index e09d0e6c..f611423a 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -27,5 +27,5 @@ export const environment = { emulator : { enabled: false, }, - version: '2.5.0', + version: '2.5.1', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 647f1269..e23235b7 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -29,7 +29,7 @@ export const environment = { emulator : { enabled: true, }, - version: '2.5.0', + version: '2.5.1', }; /*