diff --git a/package-lock.json b/package-lock.json index 3f977af6..dcaf9b6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "vue-color": "^2.8.1", "vue-d3-sunburst": "git+https://github.com/ErikBjare/Vue.D3.sunburst.git#patch-1", "vue-datetime": "^1.0.0-beta.13", + "vue-i18n": "^8.28.2", "vuedraggable": "^2.24.3", "weekstart": "^1.0.1", "xss": "^1.0.14" @@ -226,6 +227,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2232,6 +2234,7 @@ "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", "license": "MIT", + "peer": true, "dependencies": { "@types/hammerjs": "^2.0.36" }, @@ -5092,6 +5095,7 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -5958,6 +5962,7 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -6384,6 +6389,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6609,6 +6615,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6764,6 +6771,7 @@ "integrity": "sha512-yTX7GVyM19tEbd+y5/gA6MkVKA6K61nVYHYAivD61Hx6odVFmQsaC3/R3cWAHM1P5oVKCevBbumPljbT+tFG2w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.12.16", "@soda/friendly-errors-webpack-plugin": "^1.8.0", @@ -6864,6 +6872,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7724,6 +7733,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7802,6 +7812,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8189,6 +8200,7 @@ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -8656,6 +8668,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8931,7 +8944,8 @@ "version": "3.9.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/chokidar": { "version": "3.6.0", @@ -9275,6 +9289,7 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -9688,6 +9703,7 @@ "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -10307,6 +10323,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -10464,8 +10481,7 @@ "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debounce": { "version": "1.2.1", @@ -11550,6 +11566,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -11662,6 +11679,7 @@ "integrity": "sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "eslint-utils": "^3.0.0", "natural-compare": "^1.4.0", @@ -12511,6 +12529,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12832,6 +12851,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14980,6 +15000,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -16238,7 +16259,8 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-beautify": { "version": "1.15.4", @@ -16547,7 +16569,8 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.3.1.tgz", "integrity": "sha512-zn47Ti4FJT9zdF+YBBLWJsfKF/fYQHkrYlBeB5Ez5e2PjW7SoIxr43yehAne2HruulIoid4NKZZxO0dHBygCtQ==", - "license": "(Apache-2.0 OR MIT)" + "license": "(Apache-2.0 OR MIT)", + "peer": true }, "node_modules/keyv": { "version": "4.5.4", @@ -16618,6 +16641,7 @@ "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -17015,6 +17039,7 @@ "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", "license": "MIT", + "peer": true, "engines": { "node": "*" } @@ -17336,6 +17361,7 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", + "peer": true, "engines": { "node": "*" } @@ -18240,6 +18266,7 @@ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", "license": "MIT", + "peer": true, "dependencies": { "@vue/devtools-api": "^6.6.3", "vue-demi": "^0.14.10" @@ -18421,6 +18448,7 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -18478,6 +18506,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -19107,6 +19136,7 @@ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "devOptional": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -19347,6 +19377,7 @@ "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.5.0.tgz", "integrity": "sha512-3PUXWmomwutoZfydC+lJwK1bKCh6sK6jZGB31RUX6+4EXzsbkDZrK4/sVR7gBrvJaEIwpTVyxQUAd29FKkmVdw==", "license": "MIT", + "peer": true, "dependencies": { "hammerjs": "^2.0.8" } @@ -19413,6 +19444,7 @@ "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "pug-code-gen": "^3.0.3", "pug-filters": "^4.0.0", @@ -19726,6 +19758,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20415,6 +20448,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -20474,6 +20508,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -21967,6 +22002,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -22087,6 +22123,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -22344,7 +22381,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tslint": { "version": "5.20.1", @@ -22683,6 +22721,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -22909,6 +22948,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -23033,6 +23073,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", "license": "MIT", + "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -23085,6 +23126,7 @@ "resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.10.tgz", "integrity": "sha512-23juM9tdCaHTX5vyIQ7XBzsfZU0Hny+gSTwniLrfFcmw9DOm7pi3+h9iEBsoZMp5rX6KNqWwc1MF0fkAmWVuoQ==", "license": "(Apache-2.0 OR MIT)", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/visjs" @@ -23120,6 +23162,7 @@ "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.7.tgz", "integrity": "sha512-E3L03G3+trvc/X4LXvBfih3YIHcKS2WrP0XTdZefr6W6Qi/2nNCqZfe4JFfJU6DcQLm6Gxqj2Pfl+02859oL5A==", "license": "(Apache-2.0 OR MIT)", + "peer": true, "engines": { "node": ">=8" }, @@ -23137,6 +23180,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -23259,6 +23303,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -23282,6 +23327,7 @@ "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-sfc": "2.7.16", "csstype": "^3.1.0" @@ -23563,6 +23609,13 @@ "dev": true, "license": "MIT" }, + "node_modules/vue-i18n": { + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.28.2.tgz", + "integrity": "sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA==", + "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "license": "MIT" + }, "node_modules/vue-loader": { "version": "17.4.2", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", @@ -23753,6 +23806,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.3.tgz", "integrity": "sha512-LLBBA4oLmT7sZdHiYE/PeVuifOxYyE2uL/V+9VQP7YSYdJU7bSf7H8bZRRxW8kEPMkmVjnrXmoR3oejIdX0xbg==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -24414,6 +24468,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -24488,7 +24543,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/weekstart/-/weekstart-1.1.0.tgz", "integrity": "sha512-ZO3I7c7J9nwGN1PZKZeBYAsuwWEsCOZi5T68cQoVNYrzrpp5Br0Bgi0OF4l8kH/Ez7nKfxa5mSsXjsgris3+qg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/whatwg-encoding": { "version": "2.0.0", @@ -24980,6 +25036,7 @@ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -25337,6 +25394,7 @@ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -25769,6 +25827,7 @@ "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", "license": "MIT", + "peer": true, "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" diff --git a/package.json b/package.json index f6a0fa50..4c8d74ba 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test": "jest", "test:unit": "jest --selectProjects jsdom", "test:e2e": "vue-cli-service test:e2e", + "check:locales": "node scripts/check-locales.mjs", "lint": "vue-cli-service lint", "lint_old": "eslint --ignore-path=.gitignore --ext .vue,.js,.ts ." }, @@ -69,6 +70,7 @@ "vue-color": "^2.8.1", "vue-d3-sunburst": "git+https://github.com/ErikBjare/Vue.D3.sunburst.git#patch-1", "vue-datetime": "^1.0.0-beta.13", + "vue-i18n": "^8.28.2", "vuedraggable": "^2.24.3", "weekstart": "^1.0.1", "xss": "^1.0.14" diff --git a/scripts/check-locales.mjs b/scripts/check-locales.mjs new file mode 100644 index 00000000..e10adfff --- /dev/null +++ b/scripts/check-locales.mjs @@ -0,0 +1,200 @@ +#!/usr/bin/env node +/** + * Validates i18n locale files: key parity vs en, placeholder consistency, untranslated strings. + * Usage: node scripts/check-locales.mjs + */ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const LOCALES_DIR = path.join(__dirname, '../src/i18n/locales'); +const LOCALES = ['en', 'uk', 'de', 'ru']; + +/** Substrings: identical en/value is OK when value contains any of these (case-insensitive). */ +const ALLOWLIST_SUBSTRINGS = [ + 'activitywatch', + 'discord', + 'github', + 'twitter', + 'facebook', + 'patreon', + 'reddit', + 'producthunt', + 'alternativeto', + 'opentelemetry', + 'json', + 'csv', + 'api', + 'host', + 'bucket', + 'watcher', + 'afk', + 'stopwatch', + 'timespiral', + 'devtools', + 'devmode', + 'hostname', + 'http', + 'forum', + 'patreon', + 'liberapay', + 'opencollective', + 'mozilla', + 'chrome', + 'android', + 'ios', + 'linux', + 'windows', + 'macos', + 'npm', + 'vue', + 'pinia', + 'id', + 'url', + 'regex', + 'score', + 'timeline', + 'graph', + 'query', + 'trends', + 'report', + 'search', + 'settings', + 'home', + 'tools', + 'theme', + 'auto', + 'light', + 'dark', + 'monday', + 'saturday', + 'sunday', + 'version', + 'experiment', + 'wip', + 'demo', + 'cancel', + 'save', + 'open', + 'more', + 'delete', + 'import', + 'export', + 'confirm', + 'enabled', + 'disabled', + 'loading', + 'refresh', + 'options', + 'filters', + 'remove', + 'start', + 'end', + 'running', + 'history', + 'documentation', + 'website', + 'forum', +]; + +function loadLocale(code) { + const filePath = path.join(LOCALES_DIR, `${code}.ts`); + let text = fs.readFileSync(filePath, 'utf8'); + text = text.replace(/export\s+default\s+/, '').replace(/;\s*$/, ''); + // Locale files are static object literals only. + // eslint-disable-next-line no-new-func + return new Function(`return (${text})`)(); +} + +function flatten(obj, prefix = '') { + const out = {}; + for (const [key, value] of Object.entries(obj)) { + const pathKey = prefix ? `${prefix}.${key}` : key; + if (value !== null && typeof value === 'object' && !Array.isArray(value)) { + Object.assign(out, flatten(value, pathKey)); + } else if (typeof value === 'string') { + out[pathKey] = value; + } + } + return out; +} + +function placeholders(s) { + const m = s.match(/\{[a-zA-Z]+\}/g); + return m ? [...new Set(m)].sort().join(',') : ''; +} + +function isAllowlistedIdentical(enValue, targetValue) { + if (enValue !== targetValue) return false; + const lower = enValue.toLowerCase(); + if (enValue.length <= 3) return true; + return ALLOWLIST_SUBSTRINGS.some(sub => lower.includes(sub)); +} + +let failed = false; + +const enFlat = flatten(loadLocale('en')); + +for (const code of LOCALES) { + if (code === 'en') continue; + const flat = flatten(loadLocale(code)); + const enKeys = new Set(Object.keys(enFlat)); + const keys = new Set(Object.keys(flat)); + + const missing = [...enKeys].filter(k => !keys.has(k)); + const extra = [...keys].filter(k => !enKeys.has(k)); + + if (missing.length) { + failed = true; + console.error(`\n[${code}] Missing keys (${missing.length}):`); + missing.slice(0, 20).forEach(k => console.error(` - ${k}`)); + if (missing.length > 20) console.error(` ... and ${missing.length - 20} more`); + } + if (extra.length) { + failed = true; + console.error(`\n[${code}] Extra keys (${extra.length}):`); + extra.slice(0, 20).forEach(k => console.error(` - ${k}`)); + } + + const placeholderMismatches = []; + const untranslated = []; + + for (const key of enKeys) { + if (!keys.has(key)) continue; + const enVal = enFlat[key]; + const val = flat[key]; + if (placeholders(enVal) !== placeholders(val)) { + placeholderMismatches.push({ key, en: placeholders(enVal), got: placeholders(val) }); + } + if (key.startsWith('common.language')) continue; + if (enVal === val && !isAllowlistedIdentical(enVal, val)) { + untranslated.push(key); + } + } + + if (placeholderMismatches.length) { + failed = true; + console.error(`\n[${code}] Placeholder mismatches (${placeholderMismatches.length}):`); + placeholderMismatches.slice(0, 15).forEach(({ key, en, got }) => + console.error(` - ${key}: en [${en}] vs [${got}]`) + ); + } + + if (untranslated.length) { + console.warn(`\n[${code}] Possibly untranslated (identical to en, ${untranslated.length}):`); + untranslated.slice(0, 15).forEach(k => console.warn(` - ${k}: "${enFlat[k].slice(0, 60)}..."`)); + if (untranslated.length > 15) console.warn(` ... and ${untranslated.length - 15} more`); + } +} + +const enCount = Object.keys(enFlat).length; +console.log(`\nChecked ${LOCALES.join(', ')} — ${enCount} keys in en.`); + +if (failed) { + console.error('\nLocale check FAILED.\n'); + process.exit(1); +} + +console.log('Locale check passed (keys + placeholders).\n'); +process.exit(0); diff --git a/src/components/Footer.vue b/src/components/Footer.vue index 8ad67c61..98e78ae0 100644 --- a/src/components/Footer.vue +++ b/src/components/Footer.vue @@ -1,47 +1,46 @@ diff --git a/src/views/settings/ReleaseNotificationSettings.vue b/src/views/settings/ReleaseNotificationSettings.vue index ba172b0b..b7cfe8d5 100644 --- a/src/views/settings/ReleaseNotificationSettings.vue +++ b/src/views/settings/ReleaseNotificationSettings.vue @@ -2,28 +2,29 @@ div div.d-flex.justify-content-between.align-items-center div - h5.mb-0 Check for new releases + h5.mb-0 {{ $t('settings.releaseNotification.title') }} div - b-form-checkbox(v-model="isEnabled" switch) + b-form-checkbox(v-model="isEnabled", switch) small.text-muted - | When enabled, the web UI checks once per day for a new ActivityWatch release and shows a hint if one is available. + | {{ $t('settings.releaseNotification.help') }}