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 @@
div.container(style="color: #555; font-size: 0.9em")
div.mb-2
- | Made with
+ | {{ $t('footer.madeWith') }}
a(href="https://activitywatch.net/donate/", target="_blank" rel="noopener noreferrer")
icon(name="heart" scale=0.75 style="fill: #E55")
- | by the #[a(href="http://activitywatch.net/contributors/") ActivityWatch developers]
+ | {{ $t('footer.byDevs') }}
div
span.mt-2(v-if="info", style="color: #888; font-size: 0.8em")
span.mr-2
- b Host:
+ b {{ $t('footer.host') }}
| {{info.hostname}}
span
- b Version:
+ b {{ $t('footer.version') }}
| {{info.version}}
div(style="font-size: 0.9em; opacity: 0.8; fill: #88F")
div.float-none.float-md-right.my-2
a(href="https://github.com/ActivityWatch/activitywatch/issues/new/choose", target="_blank" rel="noopener noreferrer").mr-3
icon(name="bug")
- | Report a bug
+ | {{ $t('footer.reportBug') }}
a(href="https://forum.activitywatch.net/c/support", target="_blank" rel="noopener noreferrer").mr-3
icon(name="question-circle")
- | Ask for help
+ | {{ $t('footer.askHelp') }}
a(href="https://forum.activitywatch.net/c/features", target="_blank" rel="noopener noreferrer")
icon(name="vote-yea")
- | Vote on features
+ | {{ $t('footer.voteFeatures') }}
div.float-none.float-md-left.my-2
a(href="https://twitter.com/ActivityWatchIt", target="_blank" rel="noopener noreferrer")
icon(name="brands/twitter")
- | Twitter
+ | {{ $t('footer.twitter') }}
a(href="https://github.com/ActivityWatch", target="_blank" rel="noopener noreferrer").ml-3
icon(name="brands/github")
- | GitHub
+ | {{ $t('footer.github') }}
a(href="https://www.reddit.com/r/activitywatch/", target="_blank" rel="noopener noreferrer").ml-3
icon(name="brands/reddit")
- | Reddit
+ | {{ $t('footer.reddit') }}
a(href="https://activitywatch.net/donate/", target="_blank" rel="noopener noreferrer").ml-3
icon(name="hand-holding-heart")
- | Donate
+ | {{ $t('footer.donate') }}
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') }}