diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 82ad5b13b..5bc546398 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -27,7 +27,7 @@ export default defineConfig( settings: { "vue-i18n": { localeDir: "src/i18n/en.json", - messageSyntaxVersion: "^9.0.0", // For now this on v9, we can change it updating vue-i18n to v11 + messageSyntaxVersion: "^11.0.0", }, }, rules: { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 64068d85b..9dac99a82 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,13 +24,13 @@ "srt-support-for-html5-videos": "^2.6.11", "three": "^0.182.0", "vue": "^3.4.21", - "vue-i18n": "^9.10.2", + "vue-i18n": "^11.4.4", "vue-router": "^4.3.0" }, "devDependencies": { "@eslint/js": "^10.0.1", "@intlify/eslint-plugin-vue-i18n": "^4.4.0", - "@intlify/unplugin-vue-i18n": "^4.0.0", + "@intlify/unplugin-vue-i18n": "^11.2.3", "@playwright/test": "^1.54.1", "@types/node": "^24.1.0", "@vitejs/plugin-vue": "^5.0.4", @@ -938,24 +938,24 @@ } }, "node_modules/@intlify/bundle-utils": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-8.0.0.tgz", - "integrity": "sha512-1B++zykRnMwQ+20SpsZI1JCnV/YJt9Oq7AGlEurzkWJOFtFAVqaGc/oV36PBRYeiKnTbY9VYfjBimr2Vt42wLQ==", + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-11.2.3.tgz", + "integrity": "sha512-9mrJyUJGPFJCIFGthvIFT58CknG701z9D0VRtLBtat3teo0fisP3Q6bo/t9YHnljBTEZ42hYm1ukn16LfLkRRg==", "dev": true, "license": "MIT", "dependencies": { - "@intlify/message-compiler": "^9.4.0", - "@intlify/shared": "^9.4.0", + "@intlify/message-compiler": "~11.4.2", + "@intlify/shared": "~11.4.2", "acorn": "^8.8.2", + "esbuild": "^0.25.4", "escodegen": "^2.1.0", "estree-walker": "^2.0.2", "jsonc-eslint-parser": "^2.3.0", - "mlly": "^1.2.0", - "source-map-js": "^1.0.1", + "source-map-js": "^1.2.1", "yaml-eslint-parser": "^1.2.2" }, "engines": { - "node": ">= 14.16" + "node": ">= 22.13" }, "peerDependenciesMeta": { "petite-vue-i18n": { @@ -966,36 +966,6 @@ } } }, - "node_modules/@intlify/bundle-utils/node_modules/@intlify/message-compiler": { - "version": "9.14.5", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", - "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@intlify/shared": "9.14.5", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/bundle-utils/node_modules/@intlify/shared": { - "version": "9.14.5", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", - "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, "node_modules/@intlify/bundle-utils/node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -1067,7 +1037,6 @@ "version": "11.4.4", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.4.tgz", "integrity": "sha512-w/vItlylrAmhebkIbVl5YY8XMCtj8Mb2g70ttxktMYuf5AuRahgEHL2iLgLIsZBIbTSgs4hkUo7ucCL0uTJvOg==", - "dev": true, "license": "MIT", "dependencies": { "@intlify/devtools-types": "11.4.4", @@ -1085,7 +1054,6 @@ "version": "11.4.4", "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.4.tgz", "integrity": "sha512-PcBLmGmDQsTSVV911P8upzpcLJO1CNVYi/IH6bGnLR2nA+0L963+kXN1ZrisTEnbtw2ewN6HMMSldqzjronA0Q==", - "dev": true, "license": "MIT", "dependencies": { "@intlify/core-base": "11.4.4", @@ -1180,7 +1148,6 @@ "version": "11.4.4", "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.4.tgz", "integrity": "sha512-vn0OAV9pYkJlPPmgnsSm5eAG3mL0+9C/oaded2JY9jmxBbhmUXT3TcAUY8WRgLY9Hte7lkUJKpXrVlYjMXBD2w==", - "dev": true, "license": "MIT", "dependencies": { "@intlify/shared": "11.4.4", @@ -1197,7 +1164,6 @@ "version": "11.4.4", "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.4.tgz", "integrity": "sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 22" @@ -1207,51 +1173,93 @@ } }, "node_modules/@intlify/unplugin-vue-i18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-4.0.0.tgz", - "integrity": "sha512-q2Mhqa/mLi0tulfLFO4fMXXvEbkSZpI5yGhNNsLTNJJ41icEGUuyDe+j5zRZIKSkOJRgX6YbCyibTDJdRsukmw==", + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-11.2.3.tgz", + "integrity": "sha512-fbPHjOVAkxrPnbhAs6PTNJlfLOJj35ZqYh8CZ9OpeKZiZoulA9lkvrWYP3kfsZ5K/CG9jIHXxpb1/mf5n/mBYA==", "dev": true, "license": "MIT", "dependencies": { - "@intlify/bundle-utils": "^8.0.0", - "@intlify/shared": "^9.4.0", + "@eslint-community/eslint-utils": "^4.4.0", + "@intlify/bundle-utils": "11.2.3", + "@intlify/shared": "~11.4.2", + "@intlify/vue-i18n-extensions": "^8.0.0", "@rollup/pluginutils": "^5.1.0", - "@vue/compiler-sfc": "^3.2.47", + "@typescript-eslint/scope-manager": "^8.13.0", + "@typescript-eslint/typescript-estree": "^8.13.0", "debug": "^4.3.3", "fast-glob": "^3.2.12", - "js-yaml": "^4.1.0", - "json5": "^2.2.3", - "pathe": "^1.0.0", + "pathe": "^2.0.3", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2", - "unplugin": "^1.1.0" + "unplugin": "^2.3.4", + "vue": "~3.5.34" }, "engines": { - "node": ">= 14.16" + "node": ">= 22.13" }, "peerDependencies": { "petite-vue-i18n": "*", - "vue-i18n": "*", - "vue-i18n-bridge": "*" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25", + "vue-i18n": "*" }, "peerDependenciesMeta": { "petite-vue-i18n": { "optional": true }, + "vite": { + "optional": true + }, "vue-i18n": { "optional": true + } + } + }, + "node_modules/@intlify/vue-i18n-extensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@intlify/vue-i18n-extensions/-/vue-i18n-extensions-8.0.0.tgz", + "integrity": "sha512-w0+70CvTmuqbskWfzeYhn0IXxllr6mU+IeM2MU0M+j9OW64jkrvqY+pYFWrUnIIC9bEdij3NICruicwd5EgUuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.24.6", + "@intlify/shared": "^10.0.0", + "@vue/compiler-dom": "^3.2.45", + "vue-i18n": "^10.0.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@intlify/shared": "^9.0.0 || ^10.0.0 || ^11.0.0", + "@vue/compiler-dom": "^3.0.0", + "vue": "^3.0.0", + "vue-i18n": "^9.0.0 || ^10.0.0 || ^11.0.0" + }, + "peerDependenciesMeta": { + "@intlify/shared": { + "optional": true }, - "vue-i18n-bridge": { + "@vue/compiler-dom": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-i18n": { "optional": true } } }, - "node_modules/@intlify/unplugin-vue-i18n/node_modules/@intlify/shared": { - "version": "9.14.5", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", - "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", + "node_modules/@intlify/vue-i18n-extensions/node_modules/@intlify/core-base": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.8.tgz", + "integrity": "sha512-FoHslNWSoHjdUBLy35bpm9PV/0LVI/DSv9L6Km6J2ad8r/mm0VaGg06C40FqlE8u2ADcGUM60lyoU7Myo4WNZQ==", "dev": true, "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "10.0.8", + "@intlify/shared": "10.0.8" + }, "engines": { "node": ">= 16" }, @@ -1259,6 +1267,58 @@ "url": "https://github.com/sponsors/kazupon" } }, + "node_modules/@intlify/vue-i18n-extensions/node_modules/@intlify/message-compiler": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.8.tgz", + "integrity": "sha512-DV+sYXIkHVd5yVb2mL7br/NEUwzUoLBsMkV3H0InefWgmYa34NLZUvMCGi5oWX+Hqr2Y2qUxnVrnOWF4aBlgWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@intlify/shared": "10.0.8", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/vue-i18n-extensions/node_modules/@intlify/shared": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.8.tgz", + "integrity": "sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/vue-i18n-extensions/node_modules/vue-i18n": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.8.tgz", + "integrity": "sha512-mIjy4utxMz9lMMo6G9vYePv7gUFt4ztOMhY9/4czDJxZ26xPeJ49MAGa9wBAE3XuXbYCrtVPmPxNjej7JJJkZQ==", + "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "dev": true, + "license": "MIT", + "dependencies": { + "@intlify/core-base": "10.0.8", + "@intlify/shared": "10.0.8", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1277,12 +1337,55 @@ "node": ">=12" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2128,13 +2231,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/@vitest/snapshot": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", @@ -2150,13 +2246,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -2682,13 +2771,6 @@ "node": ">= 0.8" } }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, "node_modules/core-js": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", @@ -4498,26 +4580,6 @@ "node": ">=8" } }, - "node_modules/mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4794,9 +4856,9 @@ "license": "MIT" }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, @@ -4829,25 +4891,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/playwright": { "version": "1.60.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", @@ -5651,13 +5694,6 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/ufo": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", - "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", - "dev": true, - "license": "MIT" - }, "node_modules/underscore": { "version": "1.13.8", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", @@ -5695,17 +5731,19 @@ } }, "node_modules/unplugin": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", - "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.14.0", + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.12.0" } }, "node_modules/uri-js": { @@ -5828,13 +5866,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-node/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/vite-plugin-checker": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.10.3.tgz", @@ -5999,13 +6030,6 @@ } } }, - "node_modules/vitest/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -6059,18 +6083,18 @@ } }, "node_modules/vue-i18n": { - "version": "9.14.5", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz", - "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==", - "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "version": "11.4.4", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.4.tgz", + "integrity": "sha512-gIbXVSFQV4jcSJxfwdZ5zSZmZ+12CnX0K3vBkRSd6Zn+HSzCp+QwUgPwpD/uN0oKNKI9RzlUXPKVedEuMgNG0A==", "license": "MIT", "dependencies": { - "@intlify/core-base": "9.14.5", - "@intlify/shared": "9.14.5", + "@intlify/core-base": "11.4.4", + "@intlify/devtools-types": "11.4.4", + "@intlify/shared": "11.4.4", "@vue/devtools-api": "^6.5.0" }, "engines": { - "node": ">= 16" + "node": ">= 22" }, "funding": { "url": "https://github.com/sponsors/kazupon" @@ -6079,50 +6103,6 @@ "vue": "^3.0.0" } }, - "node_modules/vue-i18n/node_modules/@intlify/core-base": { - "version": "9.14.5", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", - "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==", - "license": "MIT", - "dependencies": { - "@intlify/message-compiler": "9.14.5", - "@intlify/shared": "9.14.5" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/vue-i18n/node_modules/@intlify/message-compiler": { - "version": "9.14.5", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", - "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", - "license": "MIT", - "dependencies": { - "@intlify/shared": "9.14.5", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/vue-i18n/node_modules/@intlify/shared": { - "version": "9.14.5", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", - "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, "node_modules/vue-router": { "version": "4.6.4", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index c75b1735c..d625848bc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,7 +38,7 @@ "srt-support-for-html5-videos": "^2.6.11", "three": "^0.182.0", "vue": "^3.4.21", - "vue-i18n": "^9.10.2", + "vue-i18n": "^11.4.4", "vue-router": "^4.3.0" }, "overrides": { @@ -48,7 +48,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@intlify/eslint-plugin-vue-i18n": "^4.4.0", - "@intlify/unplugin-vue-i18n": "^4.0.0", + "@intlify/unplugin-vue-i18n": "^11.2.3", "@playwright/test": "^1.54.1", "@types/node": "^24.1.0", "@vitejs/plugin-vue": "^5.0.4", diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index e28df778e..8461bf777 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -1,132 +1,133 @@ // i18n.js import { createI18n } from 'vue-i18n'; - -// Import translations (alphabetical order) -import ar from './ar.json'; -import cz from './cz.json'; -import de from './de.json'; -import el from './el.json'; +import { nextTick } from 'vue'; import en from './en.json'; -import es from './es.json'; -import fr from './fr.json'; -import he from './he.json'; -import hu from './hu.json'; -import is from './is.json'; -import it from './it.json'; -import ja from './ja.json'; -import ko from './ko.json'; -import nl from './nl.json'; -import nlBE from './nl-be.json'; -import pl from './pl.json'; -import pt from './pt.json'; -import ptBR from './pt-br.json'; -import ro from './ro.json'; -import ru from './ru.json'; -import sk from './sk.json'; -import svSE from './sv-se.json'; -import ua from './ua.json'; -import zhCN from './zh-cn.json'; -import zhTW from './zh-tw.json'; - -interface LocaleMap { [key: string]: string; } - -interface TranslationMessages { - [key: string]: string | TranslationMessages; -} - -// Map of all imported translation modules (alphabetical order) -// This is the single source for all translations -const translationModules: Record = { - ar, cz, de, el, en, es, fr, he, hu, is, it, ja, ko, nl, nlBE, pl, pt, ptBR, ro, ru, sk, svSE, ua, zhCN, zhTW -}; - -// Shared list of available locales for the application -// Auto-generated from translation modules -export const availableLocales: LocaleMap = Object.keys(translationModules).reduce((acc, key) => { - // Convert internal keys to their display format - const displayMap: { [key: string]: string } = { - nlBE: 'nl-be', - ptBR: 'pt-br', - svSE: 'sv-se', - zhCN: 'zh-cn', - zhTW: 'zh-tw', - }; - acc[key] = displayMap[key] || key; - return acc; -}, {} as LocaleMap); -// Maps internal locale names to standard BCP 47 codes (for navigator.language) -export const internalToStandardLocaleMap: { [key: string]: string } = { +type MessageSchema = typeof en; + +// All the locales in alphabetical order +export const availableLocales: Record = { + // internal keys: 'display value' + ar: 'ar', + cz: 'cz', + de: 'de', + el: 'el', + en: 'en', + es: 'es', + fr: 'fr', + he: 'he', + hu: 'hu', + is: 'is', + it: 'it', + ja: 'ja', + ko: 'ko', + nl: 'nl', nlBE: 'nl-be', + pl: 'pl', + pt: 'pt', ptBR: 'pt-br', + ro: 'ro', + ru: 'ru', + sk: 'sk', svSE: 'sv-se', + ua: 'ua', zhCN: 'zh-cn', zhTW: 'zh-tw', - cz: 'cs', - ua: 'uk', }; +const availableLocalesMap = new Map(Object.entries(availableLocales)); + +// Maps internal locale names to standard BCP 47 codes (for navigator.language) +const internalToStandard = new Map([ + ['nlBE', 'nl-be'], + ['ptBR', 'pt-br'], + ['svSE', 'sv-se'], + ['zhCN', 'zh-cn'], + ['zhTW', 'zh-tw'], + ['cz', 'cs'], + ['ua', 'uk'], +]); + export function toStandardLocale(locale: string): string { - return internalToStandardLocaleMap[locale] || locale; + return internalToStandard.get(locale) ?? locale; } export function detectLocale(): string { const browserLocale = navigator.language.toLowerCase(); // Map of browser locale codes to internal locale keys - const browserToInternalMap: LocaleMap = { - 'pt-br': 'ptBR', - 'zh-tw': 'zhTW', - 'zh-cn': 'zhCN', - 'zh': 'zhCN', - 'sv-se': 'svSE', - 'sv': 'svSE', - 'nl-be': 'nlBE', - }; - - // Check for exact matches first (including variants like pt-br) - if (browserToInternalMap[browserLocale]) { - return browserToInternalMap[browserLocale]; - } - - // Check for language prefix matches (e.g., 'en-US' -> 'en') - const languagePrefix = browserLocale.split('-')[0]; - - // If the language prefix exists in our translations, use it - if (translationModules[languagePrefix]) { - return languagePrefix; + const browserToInternalMap = new Map([ + ['pt-br', 'ptBR'], + ['zh-tw', 'zhTW'], + ['zh-cn', 'zhCN'], + ['zh', 'zhCN'], + ['sv-se', 'svSE'], + ['sv', 'svSE'], + ['nl-be', 'nlBE'], + ]); + const mappedLocale = browserToInternalMap.get(browserLocale); + if (mappedLocale !== undefined) { + return mappedLocale; } - - // Try browser-specific mappings for the prefix - if (browserToInternalMap[languagePrefix]) { - return browserToInternalMap[languagePrefix]; - } - - return 'en'; // Default fallback + const prefix = browserLocale.split('-')[0]; + return availableLocalesMap.get(prefix) ?? 'en'; } // List of RTL languages export const rtlLanguages = ['he', 'ar']; // Function to check if locale is RTL -export const isRtl = (locale?: string) => { - const currentLocale = locale || i18n.global.locale.value; - return rtlLanguages.includes(currentLocale); -}; +export const isRtl = (locale?: string) => rtlLanguages.includes(locale || i18n.global.locale.value); -export function setLocale(locale: string) { - // according to doc u only need .value if legacy: false but they lied - // https://vue-i18n.intlify.dev/guide/essentials/scope.html#local-scope-1 +function setLanguage(locale: string) { i18n.global.locale.value = locale; + document.querySelector('html')?.setAttribute('lang', toStandardLocale(locale)); } // Create i18n instance with auto-generated messages from imported modules -const i18n = createI18n({ - locale: detectLocale(), +const i18n = createI18n({ + locale: 'en', fallbackLocale: 'en', - // expose i18n.global for outside components legacy: false, - messages: translationModules, + messages: { en }, }); +// Set English as initial language and update the html lang +setLanguage('en'); + +// import.meta.glob is vite-specific +// here we are preloading all json files as lazy chunks, except 'en.json' since this one will be always loaded. +const localeModules = import.meta.glob<{ default: Record }>('./!(en).json'); + +export async function setLocale(locale: string) { + // If the locale doesn't exist in our list, fallback to English + if (!availableLocalesMap.has(locale)) { + setLanguage('en'); + return; + } + // If the locale is already loaded just switch to it + if (i18n.global.availableLocales.includes(locale)) { + setLanguage(locale); + return; + } + // But if isn't loaded, we will load it dynamically. + try { + const fileName = availableLocalesMap.get(locale); + if (!fileName) { + setLanguage('en'); + return; + } + const messages = (await localeModules[`./${fileName}.json`]()).default; + i18n.global.setLocaleMessage(locale, messages); + setLanguage(locale); + await nextTick(); + } catch (e) { + console.error("Failed to set locale:", e); + setLanguage('en'); // Just in case that anything fails, fallback to english + } +} + +const detected = detectLocale(); +if (detected !== 'en') void setLocale(detected); + export default i18n; diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index eb6e304c6..3fd3315c8 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -193,7 +193,7 @@ router.beforeResolve(async (to, from, next) => { try { await validateLogin(isPublicRoute); } catch (_error) { - mutations.setCurrentUser(getters.anonymous()); + await mutations.setCurrentUser(getters.anonymous()); } } diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js index f01f8fb18..7a130fd73 100644 --- a/frontend/src/store/mutations.js +++ b/frontend/src/store/mutations.js @@ -385,7 +385,7 @@ export const mutations = { state.reload = value; emitStateChanged(); }, - setCurrentUser: (value) => { + setCurrentUser: async (value) => { try { // If value is null or undefined, emit state change and exit early if (!value) { @@ -398,9 +398,9 @@ export const mutations = { } // Ensure locale exists and is valid if (!value.locale) { - value.locale = i18n.detectLocale(); // Default to detected locale if missing + value.locale = i18n.detectLocale(); } else { - i18n.setLocale(value.locale); + await i18n.setLocale(value.locale); } state.user = value; state.user.sorting = {}; @@ -549,7 +549,7 @@ export const mutations = { state.previewRaw = value; emitStateChanged(); }, - updateCurrentUser: (value) => { + updateCurrentUser: async (value) => { // Ensure the input is a valid object if (typeof value !== "object" || value === null) return; @@ -576,7 +576,7 @@ export const mutations = { // Handle locale change if (state.user.locale !== previousUser.locale) { - i18n.setLocale(state.user.locale); + await i18n.setLocale(state.user.locale); i18n.default.locale = state.user.locale; localStorage.setItem("userLocale", state.user.locale); } diff --git a/frontend/src/utils/auth.js b/frontend/src/utils/auth.js index f17d9a277..d81ae012d 100644 --- a/frontend/src/utils/auth.js +++ b/frontend/src/utils/auth.js @@ -17,7 +17,7 @@ export async function validateLogin(isPublicRoute = false) { throw new Error(`{"status":${res.status},"message":"${await res.text()}"}`); } const userInfo = await res.json(); - mutations.setCurrentUser(userInfo); + await mutations.setCurrentUser(userInfo); getters.isLoggedIn() if (state.user.loginMethod === "proxy") { const apiPath = getApiPath("auth/login") diff --git a/frontend/src/views/settings/Profile.vue b/frontend/src/views/settings/Profile.vue index be76d42b8..92dc6ad70 100644 --- a/frontend/src/views/settings/Profile.vue +++ b/frontend/src/views/settings/Profile.vue @@ -387,7 +387,7 @@ export default { try { const data = this.localuser; const themeChanged = state.user.customTheme !== this.localuser.customTheme; - mutations.updateCurrentUser(data); + await mutations.updateCurrentUser(data); await usersApi.update(data, [ "locale", "showHidden", @@ -425,9 +425,9 @@ export default { console.error(e); } }, - updateLocale(updatedLocale) { + async updateLocale(updatedLocale) { this.localuser.locale = updatedLocale; - this.updateSettings(); + await this.updateSettings(); }, handleSectionToggle(sectionTitle) { // Accordion logic: if clicking the same section, collapse it, otherwise expand the new one diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 7b8bafa05..7a18b71c2 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -23,6 +23,15 @@ "@/*": ["./src/*"] } }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.d.ts", "src/**/*.vue", "tests-playwright/**/*.ts", "eslint.config.js", "scripts/**/*"], + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.vue", + "tests-playwright/**/*.ts", + "scripts/**/*.js", + "eslint.config.js", + "vite.config.ts" + ], "exclude": ["node_modules", "dist", "../_docker/src/proxy/frontend/playwright.config.ts"] } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 093ae9e87..bb5fade56 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -10,6 +10,7 @@ const isDevBuild = process.env.DEV_BUILD === "true"; const plugins = [ vue(), VueI18nPlugin({ + runtimeOnly: false, include: [path.resolve(__dirname, "./src/i18n/**/*.json")], }), // Only compress in production builds @@ -38,6 +39,10 @@ export default defineConfig(() => { plugins, resolve, base: "", + define: { + __VUE_I18N_LEGACY_API__: JSON.stringify(false), + __VUE_I18N_FULL_INSTALL__: JSON.stringify(false), + }, build: { // Optimize for watch mode stability watch: isDevBuild ? { @@ -51,11 +56,7 @@ export default defineConfig(() => { input: { index: path.resolve(__dirname, "./public/index.html"), }, - output: { - manualChunks(id) { - if (id.includes("i18n/")) return "i18n"; - }, - }, + output: {}, // Better error handling in watch mode onwarn(warning, warn) { // Suppress certain warnings in dev mode