/index.ts`.
+Each locale controls what it imports — **partial translations are fully supported**.
+Missing domains fall back to `en` automatically via `fallbackLocale`.
+
+### Step 1: Create the locale directory and JSON domains
+
+```
+src/i18n/de/common.json
+```
+
+You can add only the domains you have translated.
+
+### Step 2: Register locale in app runtime
+
+Edit `src/i18n/index.ts` and add locale to:
+
+- `supportedLocales`
+- `localeAliases`
+- `messages`
+- `quasarLanguagePacks`
+
+### Step 3: Add translation files
+
+```
+src/i18n/de/auth.json
+src/i18n/de/common.json
+```
+
+### That's it
+
+- Add language label to the locale picker options (if shown in UI).
+- `npm run i18n:check` will report coverage for the new locale.
+- Missing keys automatically fall back to `en`.
diff --git a/index.html b/index.html
index 65075f75..dc0b9229 100644
--- a/index.html
+++ b/index.html
@@ -1,4 +1,4 @@
-
+
<%= productName %>
diff --git a/package-lock.json b/package-lock.json
index 89397361..ea66368d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"qrcode": "1.5.4",
"quasar": "2.18.5",
"vue": "3.5.22",
+ "vue-i18n": "11.3.2",
"vue-router": "4.5.1",
"vue3-apexcharts": "1.7.0",
"vuedraggable": "4.1.0",
@@ -40,6 +41,7 @@
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-vue": "8.7.1",
+ "postcss-nested": "^6.2.0",
"prettier": "3.3.3",
"typescript": "5.9.2"
}
@@ -307,6 +309,79 @@
}
}
},
+ "node_modules/@intlify/core-base": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.3.2.tgz",
+ "integrity": "sha512-cgsUaV/dyD6aS49UPgerIblrWeXAZHNaDWqm4LujOGC7IafSyhghGXEiSVvuDYaDPiQTP+tSFSTM1HIu7Yp1nA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/devtools-types": "11.3.2",
+ "@intlify/message-compiler": "11.3.2",
+ "@intlify/shared": "11.3.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/core-base/node_modules/@intlify/message-compiler": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.3.2.tgz",
+ "integrity": "sha512-d/awyHUkNSaGPxBxT/qlUpfRizxHX9dt55CnW03xx5p1KmMyfYHKupCnvzINX+Na8JR8LAR7y32lPKjoeQGmzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/shared": "11.3.2",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/core-base/node_modules/@intlify/shared": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.2.tgz",
+ "integrity": "sha512-x66fjdH6i+lNYPae5URSQGTjBL68Av6hi09jvC5Ci96iTkwfqrPhCj46aylQZmgMaG89rOZCIKqS7ApC8ZDVjg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/devtools-types": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.3.2.tgz",
+ "integrity": "sha512-q96G2ZZw0FNoXzejbjIf9dbfgz1xyYBZu6ZT4b5TE/55j8d1O9X5jv0k+U+L3fVe7uebPcqRQFD0ffm30i5mJA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "11.3.2",
+ "@intlify/shared": "11.3.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/devtools-types/node_modules/@intlify/shared": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.2.tgz",
+ "integrity": "sha512-x66fjdH6i+lNYPae5URSQGTjBL68Av6hi09jvC5Ci96iTkwfqrPhCj46aylQZmgMaG89rOZCIKqS7ApC8ZDVjg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
"node_modules/@intlify/message-compiler": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
@@ -1294,7 +1369,6 @@
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~6.19.2"
}
@@ -1409,7 +1483,6 @@
"integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==",
"dev": true,
"license": "BSD-2-Clause",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.16.0",
"@typescript-eslint/types": "7.16.0",
@@ -1576,7 +1649,6 @@
"integrity": "sha512-IfFNbtkbIm36O9KB8QodlwwYvTEsJb4Lll4c2IwB3VHc2gie2mSPtSzL0eYay7X2jd/2WX02FjSGTWR6OPr/zg==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12.0.0"
},
@@ -1996,7 +2068,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2126,7 +2197,6 @@
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.1.tgz",
"integrity": "sha512-E4et0h/J1U3r3EwS/WlqJCQIbepKbp6wGUmaAwJOMjHUP4Ci0gxanLa7FR3okx6p9coi4st6J853/Cb1NP0vpA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@yr/monotone-cubic-spline": "^1.0.3",
"svg.draggable.js": "^2.2.2",
@@ -2301,7 +2371,6 @@
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -2623,7 +2692,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -4148,7 +4216,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -6863,7 +6930,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -6873,6 +6939,32 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
"node_modules/postcss-selector-parser": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
@@ -6992,7 +7084,6 @@
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
@@ -7026,7 +7117,6 @@
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.18.5.tgz",
"integrity": "sha512-5ItDSsNjqBVRrC7SqcdvT1F5mghVyJ/KmaWNwnaT5mM91a7gWpT/d7wTCIFxxDbWLZdkHKI+cpdudEqnfcSw9A==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",
@@ -7384,7 +7474,6 @@
"integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -8027,8 +8116,7 @@
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/source-map": {
"version": "0.6.1",
@@ -8469,7 +8557,6 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8668,7 +8755,6 @@
"integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.14.27",
"postcss": "^8.4.13",
@@ -8706,7 +8792,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-sfc": "3.5.22",
@@ -8748,12 +8833,44 @@
"eslint": ">=6.0.0"
}
},
+ "node_modules/vue-i18n": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.2.tgz",
+ "integrity": "sha512-gmFrvM+iuf2AH4ygligw/pC7PRJ63AdRNE68E0GPlQ83Mzfyck6g6cRQC3KzkYXr+ZidR91wq+5YBmAMpkgE1A==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "11.3.2",
+ "@intlify/devtools-types": "11.3.2",
+ "@intlify/shared": "11.3.2",
+ "@vue/devtools-api": "^6.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
+ "node_modules/vue-i18n/node_modules/@intlify/shared": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.2.tgz",
+ "integrity": "sha512-x66fjdH6i+lNYPae5URSQGTjBL68Av6hi09jvC5Ci96iTkwfqrPhCj46aylQZmgMaG89rOZCIKqS7ApC8ZDVjg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
"node_modules/vue-router": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
"integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
@@ -8859,7 +8976,6 @@
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
diff --git a/package.json b/package.json
index 53788ebe..ade09964 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,10 @@
"serve": "quasar dev",
"build": "quasar build",
"lint": "eslint --ext .js,.ts,.vue ./",
- "format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
+ "format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore",
+ "i18n:check": "node scripts/i18n-check.mjs",
+ "i18n:parity": "node scripts/i18n-check.mjs --parity",
+ "i18n:placeholders": "node scripts/i18n-check.mjs --placeholders"
},
"dependencies": {
"@quasar/extras": "1.16.17",
@@ -25,6 +28,7 @@
"qrcode": "1.5.4",
"quasar": "2.18.5",
"vue": "3.5.22",
+ "vue-i18n": "11.3.2",
"vue-router": "4.5.1",
"vue3-apexcharts": "1.7.0",
"vuedraggable": "4.1.0",
@@ -42,7 +46,8 @@
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-vue": "8.7.1",
+ "postcss-nested": "^6.2.0",
"prettier": "3.3.3",
"typescript": "5.9.2"
}
-}
\ No newline at end of file
+}
diff --git a/quasar.config.js b/quasar.config.js
index af5aa71b..c918b497 100644
--- a/quasar.config.js
+++ b/quasar.config.js
@@ -11,6 +11,7 @@
const { mergeConfig } = require("vite");
const { configure } = require("quasar/wrappers");
const path = require("path");
+const postcssNested = require("postcss-nested");
require("dotenv").config();
module.exports = configure(function (/* ctx */) {
@@ -30,7 +31,7 @@ module.exports = configure(function (/* ctx */) {
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files
- boot: ["pinia", "axios", "monaco", "integrations"],
+ boot: ["pinia", "axios", "i18n", "monaco", "integrations"],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ["app.sass"],
@@ -82,8 +83,14 @@ module.exports = configure(function (/* ctx */) {
/* eslint-disable quotes */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
extendViteConf(viteConf, { isServer, isClient }) {
+ viteConf.css = mergeConfig(viteConf.css || {}, {
+ postcss: {
+ plugins: [postcssNested()],
+ },
+ });
+
viteConf.build = mergeConfig(viteConf.build, {
- chunkSizeWarningLimit: 1600,
+ chunkSizeWarningLimit: 4000,
rollupOptions: {
output: {
entryFileNames: `[hash].js`,
diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs
new file mode 100644
index 00000000..75ed51fe
--- /dev/null
+++ b/scripts/i18n-check.mjs
@@ -0,0 +1,196 @@
+#!/usr/bin/env node
+/**
+ * i18n integrity checker.
+ *
+ * Uses `en` as the source of truth. Checks every other locale against it.
+ *
+ * Checks:
+ * 1. Key parity – missing keys compared to `en`.
+ * 2. Placeholder consistency – {var}, %s, %d etc. must match for shared keys.
+ *
+ * This script NEVER blocks a merge. It reports coverage and issues
+ * so the team can track translation progress incrementally.
+ *
+ * Usage:
+ * node scripts/i18n-check.mjs [--parity] [--placeholders]
+ *
+ * Defaults to running both checks if no flags given.
+ */
+
+import { readFileSync, readdirSync, statSync } from "fs";
+import { join, resolve } from "path";
+
+// ── helpers ──────────────────────────────────────────────────────────
+
+const I18N_DIR = resolve("src/i18n");
+const EN = "en";
+
+function detectLocales() {
+ return readdirSync(I18N_DIR)
+ .filter((d) => {
+ const full = join(I18N_DIR, d);
+ return statSync(full).isDirectory() &&
+ readdirSync(full).some((f) => f.endsWith(".json"));
+ });
+}
+
+function loadJSONs(locale) {
+ const dir = join(I18N_DIR, locale);
+ const result = {};
+ for (const file of readdirSync(dir)) {
+ if (!file.endsWith(".json")) continue;
+ const raw = readFileSync(join(dir, file), "utf-8");
+ result[file] = JSON.parse(raw);
+ }
+ return result;
+}
+
+/** Flatten nested object to dot-path keys */
+function flatten(obj, prefix = "") {
+ const out = {};
+ for (const [key, val] of Object.entries(obj)) {
+ const full = prefix ? `${prefix}.${key}` : key;
+ if (typeof val === "object" && val !== null && !Array.isArray(val)) {
+ Object.assign(out, flatten(val, full));
+ } else {
+ out[full] = String(val ?? "");
+ }
+ }
+ return out;
+}
+
+/** Extract placeholders: {name}, %s, %d, {0}, etc. */
+function extractPlaceholders(str) {
+ const tokens = new Set();
+ for (const m of str.matchAll(/\{[^}]+\}/g)) tokens.add(m[0]);
+ for (const m of str.matchAll(/%[sdflxXcbo]/g)) tokens.add(m[0]);
+ return tokens;
+}
+
+// ── checks ───────────────────────────────────────────────────────────
+
+let totalIssues = 0;
+
+function checkLocale(localeName, localeData, en, runParity, runPH) {
+ let localeIssues = 0;
+ const domains = new Set([...Object.keys(en), ...Object.keys(localeData)]);
+
+ // --- parity ---
+ if (runParity) {
+ for (const domain of [...domains].sort()) {
+ const enFlat = en[domain] ? flatten(en[domain]) : {};
+ const locFlat = localeData[domain] ? flatten(localeData[domain]) : {};
+
+ const enKeys = new Set(Object.keys(enFlat));
+ const locKeys = new Set(Object.keys(locFlat));
+
+ const missing = [...enKeys].filter((k) => !locKeys.has(k));
+ const extra = [...locKeys].filter((k) => !enKeys.has(k));
+
+ if (missing.length || extra.length) {
+ console.log(` ⚠️ ${localeName}/${domain}:`);
+ if (missing.length)
+ console.log(` missing keys: ${missing.length}`);
+ if (extra.length)
+ console.log(` extra keys: ${extra.length}`);
+ localeIssues += missing.length + extra.length;
+ }
+ }
+ }
+
+ // --- placeholders ---
+ if (runPH) {
+ for (const domain of Object.keys(en)) {
+ const enFlat = en[domain] ? flatten(en[domain]) : {};
+ const locFlat = localeData[domain] ? flatten(localeData[domain]) : {};
+
+ for (const [key, enVal] of Object.entries(enFlat)) {
+ const enPH = extractPlaceholders(enVal);
+ if (enPH.size === 0) continue;
+
+ const locVal = locFlat[key];
+ if (locVal === undefined) continue;
+
+ const locPH = extractPlaceholders(locVal);
+ const missing = [...enPH].filter((p) => !locPH.has(p));
+ const extra = [...locPH].filter((p) => !enPH.has(p));
+
+ if (missing.length || extra.length) {
+ console.log(` ⚠️ ${localeName}/${domain}.${key}:`);
+ if (missing.length)
+ console.log(` missing placeholders: ${missing.join(", ")}`);
+ if (extra.length)
+ console.log(` extra placeholders: ${extra.join(", ")}`);
+ localeIssues++;
+ }
+ }
+ }
+ }
+
+ return localeIssues;
+}
+
+function coverageReport(en, localeName, localeData) {
+ const enKeys = new Set();
+ const locKeys = new Set();
+
+ for (const domain of Object.keys(en)) {
+ const enFlat = flatten(en[domain]);
+ const locFlat = localeData[domain] ? flatten(localeData[domain]) : {};
+ for (const k of Object.keys(enFlat)) enKeys.add(k);
+ for (const k of Object.keys(locFlat)) {
+ if (enKeys.has(k)) locKeys.add(k);
+ }
+ }
+
+ const total = enKeys.size;
+ const translated = locKeys.size;
+ const pct = total > 0 ? ((translated / total) * 100).toFixed(1) : 0;
+ console.log(` 📊 ${localeName}: ${translated}/${total} keys (${pct}%)`);
+}
+
+// ── main ─────────────────────────────────────────────────────────────
+
+function main() {
+ const args = process.argv.slice(2);
+ const runParity = args.length === 0 || args.includes("--parity");
+ const runPH = args.length === 0 || args.includes("--placeholders");
+
+ console.log("🔍 i18n integrity check\n");
+
+ const en = loadJSONs(EN);
+ const locales = detectLocales().filter((l) => l !== EN);
+
+ if (locales.length === 0) {
+ console.log(" No non-English locales found. Nothing to check.");
+ process.exit(0);
+ return;
+ }
+
+ for (const locale of locales.sort()) {
+ const locData = loadJSONs(locale);
+
+ // Coverage summary
+ coverageReport(en, locale, locData);
+
+ const issues = checkLocale(locale, locData, en, runParity, runPH);
+ totalIssues += issues;
+
+ if (issues === 0) {
+ console.log(` ✅ ${locale}: fully in sync with en`);
+ }
+
+ console.log("");
+ }
+
+ // Always exit 0 – this is informational, never blocks merge.
+ if (totalIssues > 0) {
+ console.log(`⚠️ ${totalIssues} i18n issue(s) found (non-blocking).`);
+ console.log(" Community needs more time to complete translations.");
+ } else {
+ console.log("✅ All locales fully in sync with en.");
+ }
+ process.exit(0);
+}
+
+main();
diff --git a/src/api/agents.js b/src/api/agents.js
index d8c2b662..928da2a9 100644
--- a/src/api/agents.js
+++ b/src/api/agents.js
@@ -1,6 +1,7 @@
import axios from "axios";
import { openURL } from "quasar";
import { router } from "@/router";
+import { i18n } from "@/i18n";
const baseUrl = "/agents";
@@ -333,7 +334,10 @@ export async function deleteRegistryValue(agent_id, path, name) {
);
return data;
} catch (e) {
- console.error("Failed to delete value:", e);
+ console.error(
+ i18n.global.t("agents.registryManager.errors.deleteValue"),
+ e,
+ );
throw e;
}
}
@@ -350,7 +354,10 @@ export async function renameRegistryValue(agentId, path, oldName, newName) {
);
return data;
} catch (e) {
- console.error("Failed to rename value:", e);
+ console.error(
+ i18n.global.t("agents.registryManager.errors.renameValue"),
+ e,
+ );
throw e;
}
}
@@ -374,7 +381,10 @@ export async function modifyRegistryValue(
);
return data;
} catch (e) {
- console.error("Failed to modify registry value:", e);
+ console.error(
+ i18n.global.t("agents.registryManager.errors.modifyValue"),
+ e,
+ );
throw e;
}
}
@@ -392,7 +402,10 @@ export async function createRegistryValue(agentId, path, name, type, data) {
);
return data;
} catch (e) {
- console.error("Failed to create registry value:", e);
+ console.error(
+ i18n.global.t("agents.registryManager.errors.createValue"),
+ e,
+ );
throw e;
}
}
diff --git a/src/api/services.js b/src/api/services.js
index dcaf38fc..06fb95a7 100644
--- a/src/api/services.js
+++ b/src/api/services.js
@@ -27,7 +27,7 @@ export async function getAgentServiceDetails(agent_id, svcname, params = {}) {
export async function editAgentServiceStartType(agent_id, svcname, payload) {
const { data } = await axios.put(
`${baseUrl}/${agent_id}/${svcname}/`,
- payload
+ payload,
);
return data;
}
@@ -35,7 +35,7 @@ export async function editAgentServiceStartType(agent_id, svcname, payload) {
export async function sendAgentServiceAction(agent_id, svcname, payload) {
const { data } = await axios.post(
`${baseUrl}/${agent_id}/${svcname}/`,
- payload
+ payload,
);
return data;
}
diff --git a/src/boot/axios.js b/src/boot/axios.js
index 05f55d52..292512e6 100644
--- a/src/boot/axios.js
+++ b/src/boot/axios.js
@@ -1,6 +1,7 @@
import axios from "axios";
import { useAuthStore } from "@/stores/auth";
import { Notify } from "quasar";
+import { i18n } from "@/i18n";
export const getBaseUrl = () => {
if (process.env.NODE_ENV === "production") {
@@ -46,9 +47,8 @@ export default function ({ app, router }) {
if (error.code && error.code === "ERR_NETWORK") {
Notify.create({
color: "negative",
- message: "Backend is offline (network error)",
- caption:
- "Open your browser's dev tools and check the console tab for more detailed error messages",
+ message: i18n.global.t("notifications.network.backendOffline"),
+ caption: i18n.global.t("notifications.network.backendOfflineHelp"),
timeout: 5000,
});
return Promise.reject({ ...error });
diff --git a/src/boot/i18n.ts b/src/boot/i18n.ts
new file mode 100644
index 00000000..1c033094
--- /dev/null
+++ b/src/boot/i18n.ts
@@ -0,0 +1,8 @@
+import { boot } from "quasar/wrappers";
+
+import { i18n, resolveInitialLocale, setLocale } from "@/i18n";
+
+export default boot(async ({ app }) => {
+ app.use(i18n);
+ await setLocale(resolveInitialLocale(), { persist: false });
+});
diff --git a/src/components/AdminManager.vue b/src/components/AdminManager.vue
index e5b82a83..514d904f 100644
--- a/src/components/AdminManager.vue
+++ b/src/components/AdminManager.vue
@@ -9,17 +9,19 @@
flat
push
icon="refresh"
- />User Administration
+ />{{ $t("settings.adminManager.title") }}
- Close
+ {{
+ $t("common.buttons.close")
+ }}
- Enable User
+ {{
+ $t("settings.adminManager.enableUser")
+ }}
@@ -55,7 +59,9 @@
- No Users
+ {{
+ $t("settings.adminManager.noUsers")
+ }}
@@ -77,7 +83,9 @@
- Edit
+ {{
+ $t("common.buttons.edit")
+ }}
- Delete
+ {{
+ $t("common.buttons.delete")
+ }}
@@ -103,7 +113,9 @@
- Reset Password
+ {{
+ $t("settings.adminManager.resetPassword")
+ }}
- Reset Two-Factor Auth
+ {{
+ $t("settings.adminManager.resetTwoFactorAuth")
+ }}
@@ -131,7 +145,9 @@
- Show Connected SSO Accounts
+ {{
+ $t("settings.adminManager.showConnectedSsoAccounts")
+ }}
- Show Active Sessions
+ {{
+ $t("settings.adminManager.showActiveSessions")
+ }}
- Close
+ {{
+ $t("common.buttons.close")
+ }}
@@ -167,7 +187,7 @@
v-if="props.row.social_accounts.length > 0"
color="primary"
dense
- >SSO{{ $t("settings.adminManager.sso") }}
{{ props.row.username }}
@@ -176,7 +196,7 @@
{{
formatDate(props.row.last_login)
}}
- Never
+ {{ $t("agents.shared.never") }}
{{ props.row.last_login_ip }}
@@ -238,7 +258,7 @@ export default {
columns: [
{
name: "is_active",
- label: "Active",
+ label: this.$t("settings.adminManager.columns.active"),
field: "is_active",
align: "left",
},
@@ -251,35 +271,35 @@ export default {
},
{
name: "username",
- label: "Username",
+ label: this.$t("settings.adminManager.columns.username"),
field: "username",
align: "left",
sortable: true,
},
{
name: "name",
- label: "Name",
+ label: this.$t("settings.common.name"),
field: "name",
align: "left",
sortable: true,
},
{
name: "email",
- label: "Email",
+ label: this.$t("settings.adminManager.columns.email"),
field: "email",
align: "left",
sortable: true,
},
{
name: "last_login",
- label: "Last Login",
+ label: this.$t("settings.adminManager.columns.lastLogin"),
field: "last_login",
align: "left",
sortable: true,
},
{
name: "last_login_ip",
- label: "Last Logon From",
+ label: this.$t("settings.adminManager.columns.lastLogonFrom"),
field: "last_login_ip",
align: "left",
sortable: true,
@@ -308,14 +328,20 @@ export default {
deleteUser(user) {
this.$q
.dialog({
- title: `Delete user ${user.username}?`,
+ title: this.$t("settings.adminManager.deleteUserTitle", {
+ username: user.username,
+ }),
cancel: true,
- ok: { label: "Delete", color: "negative" },
+ ok: { label: this.$t("common.buttons.delete"), color: "negative" },
})
.onOk(() => {
this.$axios.delete(`/accounts/${user.id}/users/`).then(() => {
this.getUsers();
- this.notifySuccess(`User ${user.username} was deleted!`);
+ this.notifySuccess(
+ this.$t("settings.adminManager.notify.userDeleted", {
+ username: user.username,
+ }),
+ );
});
});
},
@@ -345,8 +371,8 @@ export default {
return;
}
let text = !user.is_active
- ? "User enabled successfully"
- : "User disabled successfully";
+ ? this.$t("settings.adminManager.notify.userEnabled")
+ : this.$t("settings.adminManager.notify.userDisabled");
const data = {
id: user.id,
@@ -376,9 +402,11 @@ export default {
this.$q
.dialog({
- title: `Reset 2FA for ${user.username}?`,
+ title: this.$t("settings.adminManager.reset2faTitle", {
+ username: user.username,
+ }),
cancel: true,
- ok: { label: "Reset", color: "positive" },
+ ok: { label: this.$t("common.actions.reset"), color: "positive" },
})
.onOk(() => {
this.$axios
diff --git a/src/components/AgentTable.vue b/src/components/AgentTable.vue
index 8cc9d08a..0891db74 100644
--- a/src/components/AgentTable.vue
+++ b/src/components/AgentTable.vue
@@ -18,7 +18,7 @@
virtual-scroll
v-model:pagination="pagination"
:rows-per-page-options="[0]"
- no-data-label="No Agents"
+ :no-data-label="$t('dashboard.agentTable.noAgents')"
:loading="agentTableLoading"
>
@@ -52,35 +52,39 @@
- Checks Status
+ {{ $t("dashboard.agentTable.checksStatus") }}
- Patches Pending
+ {{
+ $t("dashboard.agentTable.patchesPending")
+ }}
- Pending Actions
+ {{
+ $t("dashboard.agentTable.pendingActions")
+ }}
- Agent Status
+ {{ $t("dashboard.agentTable.agentStatus") }}
- Reboot
+ {{ $t("dashboard.agentTable.reboot") }}
@@ -107,8 +111,11 @@
dense
>
- Setting is overridden by alert template:
- {{ props.row.alert_template.name }}
+ {{
+ $t("dashboard.agentTable.settingOverriddenByTemplate", {
+ name: props.row.alert_template.name,
+ })
+ }}
@@ -134,8 +141,11 @@
dense
>
- Setting is overridden by alert template:
- {{ props.row.alert_template.name }}
+ {{
+ $t("dashboard.agentTable.settingOverriddenByTemplate", {
+ name: props.row.alert_template.name,
+ })
+ }}
@@ -161,8 +171,11 @@
dense
>
- Setting is overridden by alert template:
- {{ props.row.alert_template.name }}
+ {{
+ $t("dashboard.agentTable.settingOverriddenByTemplate", {
+ name: props.row.alert_template.name,
+ })
+ }}
@@ -189,7 +202,9 @@
size="sm"
color="primary"
>
- Microsoft Windows
+ {{
+ $t("dashboard.agentTable.platform.windows")
+ }}
- Linux
+ {{
+ $t("dashboard.agentTable.platform.linux")
+ }}
- macOS
+ {{
+ $t("dashboard.agentTable.platform.macos")
+ }}
@@ -216,10 +235,14 @@
size="sm"
color="primary"
>
- Server
+ {{
+ $t("dashboard.agentTable.monitoringType.server")
+ }}
- Workstation
+ {{
+ $t("dashboard.agentTable.monitoringType.workstation")
+ }}
@@ -230,7 +253,9 @@
size="1.2em"
:color="dash_positive_color"
>
- Maintenance Mode Enabled
+ {{
+ $t("dashboard.agentTable.status.maintenanceModeEnabled")
+ }}
- Checks failing
+ {{
+ $t("dashboard.agentTable.status.checksFailing")
+ }}
- Checks warning
+ {{
+ $t("dashboard.agentTable.status.checksWarning")
+ }}
- Checks info
+ {{
+ $t("dashboard.agentTable.status.checksInfo")
+ }}
- Checks passing
+ {{
+ $t("dashboard.agentTable.status.checksPassing")
+ }}
@@ -287,7 +320,9 @@
size="1.5em"
color="primary"
>
- Patches Pending
+ {{
+ $t("dashboard.agentTable.patchesPending")
+ }}
@@ -299,10 +334,13 @@
:color="dash_warning_color"
class="cursor-pointer"
>
- Pending Action Count:
- {{ props.row.pending_actions_count }}
+
+ {{
+ $t("dashboard.agentTable.pendingActionCount", {
+ count: props.row.pending_actions_count,
+ })
+ }}
+
@@ -312,7 +350,9 @@
name="fas fa-power-off"
color="primary"
>
- Reboot required
+ {{
+ $t("dashboard.agentTable.status.rebootRequired")
+ }}
@@ -322,7 +362,9 @@
size="1.2em"
:color="dash_negative_color"
>
- Agent overdue
+ {{
+ $t("dashboard.agentTable.status.agentOverdue")
+ }}
- Agent offline
+ {{
+ $t("dashboard.agentTable.status.agentOffline")
+ }}
- Agent online
+ {{
+ $t("dashboard.agentTable.status.agentOnline")
+ }}
{{
@@ -362,7 +408,6 @@ import PendingActions from "@/components/logs/PendingActions.vue";
import AgentActionMenu from "@/components/agents/AgentActionMenu.vue";
import { runURLAction } from "@/api/core";
import { runTakeControl, runRemoteBackground } from "@/api/agents";
-import { capitalize } from "@vue/shared";
export default {
name: "AgentTable",
@@ -379,12 +424,28 @@ export default {
sortBy: "hostname",
descending: false,
},
- dashboard_overdue_text: "Show a dashboard alert when agent is overdue",
- email_overdue_text: "Send an email alert when agent is overdue",
- sms_overdue_text: "Send a SMS alert when agent is overdue",
+ dashboard_overdue_text: this.$t(
+ "dashboard.agentTable.alerts.dashboardOverdue",
+ ),
+ email_overdue_text: this.$t("dashboard.agentTable.alerts.emailOverdue"),
+ sms_overdue_text: this.$t("dashboard.agentTable.alerts.smsOverdue"),
};
},
+ watch: {
+ "$i18n.locale"() {
+ this.syncLocalizedTexts();
+ },
+ },
methods: {
+ syncLocalizedTexts() {
+ this.dashboard_overdue_text = this.$t(
+ "dashboard.agentTable.alerts.dashboardOverdue",
+ );
+ this.email_overdue_text = this.$t(
+ "dashboard.agentTable.alerts.emailOverdue",
+ );
+ this.sms_overdue_text = this.$t("dashboard.agentTable.alerts.smsOverdue");
+ },
filterTable(rows, terms, cols, cellValue) {
const hiddenFields = [
"version",
@@ -507,7 +568,12 @@ export default {
else if (category === "text") db_field = "overdue_text_alert";
else if (category === "dashboard") db_field = "overdue_dashboard_alert";
- const action = !alert_action ? "enabled" : "disabled";
+ const action = !alert_action
+ ? this.$t("dashboard.agentTable.alerts.action.enabled")
+ : this.$t("dashboard.agentTable.alerts.action.disabled");
+ const categoryLabel = this.$t(
+ `dashboard.agentTable.alerts.category.${category}`,
+ );
const data = {
[db_field]: !alert_action,
};
@@ -519,9 +585,11 @@ export default {
color: alertColor,
textColor: "black",
icon: "fas fa-check-circle",
- message: `${capitalize(category)} alerts will now be ${action} when ${
- agent.hostname
- } is overdue.`,
+ message: this.$t("dashboard.agentTable.alerts.updated", {
+ category: categoryLabel,
+ action,
+ hostname: agent.hostname,
+ }),
timeout: 5000,
});
});
diff --git a/src/components/AlertsIcon.vue b/src/components/AlertsIcon.vue
index 25800eb3..d77039f2 100644
--- a/src/components/AlertsIcon.vue
+++ b/src/components/AlertsIcon.vue
@@ -5,7 +5,9 @@
}}
- No New Alerts
+ {{
+ $t("alerts.icon.noNewAlerts")
+ }}
- Snooze alert
+ {{ $t("alerts.common.snoozeAlert") }}
- Resolve alert
+ {{ $t("alerts.common.resolveAlert") }}
- View All Alerts ({{ alertsCount }})
+ {{
+ $t("alerts.icon.viewAllAlerts", { count: alertsCount })
+ }}
@@ -112,8 +114,8 @@ export default {
snoozeAlert(alert) {
this.$q
.dialog({
- title: "Snooze Alert",
- message: "How many days to snooze alert?",
+ title: this.$t("alerts.dialog.snoozeTitle"),
+ message: this.$t("alerts.dialog.snoozeMessage"),
prompt: {
model: "",
type: "number",
@@ -135,7 +137,9 @@ export default {
.then(() => {
this.getAlerts();
this.$q.loading.hide();
- this.notifySuccess(`The alert has been snoozed for ${days} days`);
+ this.notifySuccess(
+ this.$t("alerts.notify.snoozedForDays", { days: days }),
+ );
})
.catch(() => {
this.$q.loading.hide();
@@ -155,7 +159,7 @@ export default {
.then(() => {
this.getAlerts();
this.$q.loading.hide();
- this.notifySuccess("The alert has been resolved");
+ this.notifySuccess(this.$t("alerts.notify.resolved"));
})
.catch(() => {
this.$q.loading.hide();
@@ -171,9 +175,12 @@ export default {
else return this.alertsCount;
},
pollAlerts() {
- this.poll = setInterval(() => {
- this.getAlerts();
- }, 60 * 1 * 1000);
+ this.poll = setInterval(
+ () => {
+ this.getAlerts();
+ },
+ 60 * 1 * 1000,
+ );
},
},
mounted() {
diff --git a/src/components/AlertsManager.vue b/src/components/AlertsManager.vue
index 379d2d7c..a9ff0dd4 100644
--- a/src/components/AlertsManager.vue
+++ b/src/components/AlertsManager.vue
@@ -11,17 +11,19 @@
flat
push
icon="refresh"
- />Alerts Manager
+ />{{ $t("alerts.manager.title") }}
- Close
+ {{
+ $t("alerts.common.close")
+ }}
- Enable Template
+ {{
+ $t("alerts.manager.tooltips.enableTemplate")
+ }}
- Has agent alert settings
+ {{
+ $t("alerts.manager.tooltips.hasAgentSettings")
+ }}
- Has check alert settings
+ {{
+ $t("alerts.manager.tooltips.hasCheckSettings")
+ }}
- Has task alert settings
+ {{
+ $t("alerts.manager.tooltips.hasTaskSettings")
+ }}
@@ -93,7 +103,9 @@
- Edit
+ {{
+ $t("alerts.manager.menu.edit")
+ }}
- Delete
+ {{
+ $t("alerts.manager.menu.delete")
+ }}
@@ -116,13 +130,17 @@
- Alert Exclusions
+ {{
+ $t("alerts.manager.menu.alertExclusions")
+ }}
- Close
+ {{
+ $t("alerts.common.close")
+ }}
@@ -142,9 +160,9 @@
name="done"
size="sm"
>
- Alert template has agent alert settings
+ {{
+ $t("alerts.manager.tooltips.templateHasAgentSettings")
+ }}
@@ -155,9 +173,9 @@
name="done"
size="sm"
>
- Alert template has check alert settings
+ {{
+ $t("alerts.manager.tooltips.templateHasCheckSettings")
+ }}
@@ -168,9 +186,9 @@
name="done"
size="sm"
>
- Alert template has task alert settings
+ {{
+ $t("alerts.manager.tooltips.templateHasTaskSettings")
+ }}
@@ -181,7 +199,7 @@
color="primary"
text-color="white"
size="sm"
- >Default{{ $t("alerts.manager.default") }}
@@ -190,10 +208,12 @@
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="showTemplateApplied(props.row)"
- >Show where template is applied ({{
- props.row.applied_count
- }}){{
+ $t("alerts.manager.showAppliedCount", {
+ count: props.row.applied_count,
+ })
+ }}
+
@@ -201,11 +221,14 @@
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="showAlertExclusions(props.row)"
- >Alert Exclusions ({{
- props.row.excluded_agents.length +
- props.row.excluded_clients.length +
- props.row.excluded_sites.length
- }}){{
+ $t("alerts.manager.showExclusionsCount", {
+ count:
+ props.row.excluded_agents.length +
+ props.row.excluded_clients.length +
+ props.row.excluded_sites.length,
+ })
+ }}
@@ -238,47 +261,52 @@ export default {
columns: [
{
name: "is_active",
- label: "Active",
+ label: this.$t("alerts.manager.columns.active"),
field: "is_active",
align: "left",
},
{
name: "agent_settings",
- label: "Agent Settings",
+ label: this.$t("alerts.manager.columns.agentSettings"),
field: "agent_settings",
},
{
name: "check_settings",
- label: "Check Settings",
+ label: this.$t("alerts.manager.columns.checkSettings"),
field: "check_settings",
},
{
name: "task_settings",
- label: "Task Settings",
+ label: this.$t("alerts.manager.columns.taskSettings"),
field: "task_settings",
},
- { name: "name", label: "Name", field: "name", align: "left" },
+ {
+ name: "name",
+ label: this.$t("alerts.manager.columns.name"),
+ field: "name",
+ align: "left",
+ },
{
name: "applied_to",
- label: "Applied To",
+ label: this.$t("alerts.manager.columns.appliedTo"),
field: "applied_to",
align: "left",
},
{
name: "alert_exclusions",
- label: "Alert Exclusions",
+ label: this.$t("alerts.manager.columns.alertExclusions"),
field: "alert_exclusions",
align: "left",
},
{
name: "action_name",
- label: "Failure Action",
+ label: this.$t("alerts.manager.columns.failureAction"),
field: "action_name",
align: "left",
},
{
name: "resolved_action_name",
- label: "Resolved Action",
+ label: this.$t("alerts.manager.columns.resolvedAction"),
field: "resolved_action_name",
align: "left",
},
@@ -314,9 +342,14 @@ export default {
deleteTemplate(template) {
this.$q
.dialog({
- title: `Delete alert template ${template.name}?`,
+ title: this.$t("alerts.manager.dialog.deleteTemplateTitle", {
+ name: template.name,
+ }),
cancel: true,
- ok: { label: "Delete", color: "negative" },
+ ok: {
+ label: this.$t("alerts.manager.menu.delete"),
+ color: "negative",
+ },
})
.onOk(() => {
this.$q.loading.show();
@@ -326,7 +359,9 @@ export default {
this.refresh();
this.$q.loading.hide();
this.notifySuccess(
- `Alert template ${template.name} was deleted!`,
+ this.$t("alerts.manager.notify.deleted", {
+ name: template.name,
+ }),
);
})
.catch(() => {
@@ -378,8 +413,8 @@ export default {
},
toggleEnabled(template) {
let text = !template.is_active
- ? "Template enabled successfully"
- : "Template disabled successfully";
+ ? this.$t("alerts.manager.notify.templateEnabled")
+ : this.$t("alerts.manager.notify.templateDisabled");
const data = {
id: template.id,
diff --git a/src/components/FileBar.vue b/src/components/FileBar.vue
index 9f442f83..b5ac55ff 100644
--- a/src/components/FileBar.vue
+++ b/src/components/FileBar.vue
@@ -2,93 +2,147 @@
-
+
- Add
+ {{
+ $t("dashboard.fileBar.actions.add")
+ }}
- Client
+ {{
+ $t("dashboard.table.client")
+ }}
- Site
+ {{
+ $t("dashboard.table.site")
+ }}
- Audit Log
+ {{
+ $t("dashboard.fileBar.actions.auditLog")
+ }}
- Debug Log
+ {{
+ $t("dashboard.fileBar.actions.debugLog")
+ }}
-
+
- Pending Actions
+ {{
+ $t("dashboard.agentTable.pendingActions")
+ }}
-
+
- Install Agent
+ {{
+ $t("dashboard.contextMenu.installAgent")
+ }}
- Manage Deployments
+ {{
+ $t("dashboard.fileBar.actions.manageDeployments")
+ }}
- Update Agents
+ {{
+ $t("dashboard.fileBar.actions.updateAgents")
+ }}
-
+
- Clients Manager
+ {{
+ $t("dashboard.fileBar.actions.clientsManager")
+ }}
- Script Manager
+ {{
+ $t("dashboard.fileBar.actions.scriptManager")
+ }}
- Automation Manager
+ {{
+ $t("dashboard.fileBar.actions.automationManager")
+ }}
- Alerts Manager
+ {{
+ $t("dashboard.fileBar.actions.alertsManager")
+ }}
- Permissions Manager
+ {{
+ $t("dashboard.fileBar.actions.permissionsManager")
+ }}
- User Administration
+ {{
+ $t("dashboard.fileBar.actions.userAdministration")
+ }}
- Global Settings
+ {{
+ $t("dashboard.fileBar.actions.globalSettings")
+ }}
- Code Signing
+ {{
+ $t("dashboard.fileBar.actions.codeSigning")
+ }}
-
+
@@ -120,15 +184,21 @@
v-close-popup
@click="showBulkAction('command')"
>
- Bulk Command
+ {{
+ $t("dashboard.fileBar.actions.bulkCommand")
+ }}
- Bulk Script
+ {{
+ $t("dashboard.fileBar.actions.bulkScript")
+ }}
- Bulk Patch Management
+ {{
+ $t("dashboard.fileBar.actions.bulkPatchManagement")
+ }}
- Server Maintenance
+ {{
+ $t("dashboard.fileBar.actions.serverMaintenance")
+ }}
- Clear Cache
+ {{
+ $t("dashboard.fileBar.actions.clearCache")
+ }}
- Recover All Agents
+ {{
+ $t("dashboard.fileBar.actions.recoverAllAgents")
+ }}
-
+
- Reporting Manager
+ {{
+ $t("dashboard.fileBar.actions.reportingManager")
+ }}
-
+
- Documentation
+ {{
+ $t("dashboard.fileBar.actions.documentation")
+ }}
- GitHub Repo
+ {{
+ $t("dashboard.fileBar.actions.githubRepo")
+ }}
- Bug Report
+ {{
+ $t("dashboard.fileBar.actions.bugReport")
+ }}
- Feature Request
+ {{
+ $t("dashboard.fileBar.actions.featureRequest")
+ }}
- Join Discord
+ {{
+ $t("dashboard.fileBar.actions.joinDiscord")
+ }}
@@ -308,22 +409,23 @@ export default {
},
methods: {
clearCache() {
- this.$axios
- .get("/core/clearcache/")
- .then((r) => this.notifySuccess(r.data));
+ this.$axios.get("/core/clearcache/").then(() => {
+ this.notifySuccess(this.$t("dashboard.fileBar.notify.cacheCleared"));
+ });
},
bulkRecoverAgents() {
this.$q
.dialog({
- title: "Bulk Recover All Agents?",
- message:
- "This will restart the Tactical and Mesh Agent services on all agents",
+ title: this.$t("dashboard.fileBar.dialog.bulkRecoverTitle"),
+ message: this.$t("dashboard.fileBar.dialog.bulkRecoverMessage"),
cancel: true,
})
.onOk(() => {
- this.$axios
- .get("/agents/bulkrecovery/")
- .then((r) => this.notifySuccess(r.data));
+ this.$axios.get("/agents/bulkrecovery/").then(() => {
+ this.notifySuccess(
+ this.$t("dashboard.fileBar.notify.agentsRecoveryStarted"),
+ );
+ });
});
},
openHelp(mode) {
diff --git a/src/components/FileBrowser.vue b/src/components/FileBrowser.vue
index 844aec44..6538596a 100644
--- a/src/components/FileBrowser.vue
+++ b/src/components/FileBrowser.vue
@@ -10,7 +10,9 @@
filter="filter"
no-selection-unset
selected-color="primary"
- :filter-method="(node: QTreeFileNode/*, filter */) => node.type === 'folder'"
+ :filter-method="
+ (node: QTreeFileNode /*, filter */) => node.type === 'folder'
+ "
:nodes="nodes"
@update:selected="onFolderSelection"
@lazy-load="loadNodeChildren"
@@ -26,7 +28,7 @@
:columns="tableColumns"
:loading="loading"
dense
- no-data-label="Folder is Empty"
+ :no-data-label="t('dashboard.fileBrowser.folderEmpty')"
binary-state-sort
virtual-scroll
selection="multiple"
@@ -43,7 +45,12 @@
@@ -55,7 +62,12 @@
@@ -89,6 +101,7 @@
// composition imports
import { ref, toRef, onMounted } from "vue";
import { isDefined } from "@vueuse/core";
+import { useI18n } from "vue-i18n";
// type imports
import type { QTableColumn, QTreeLazyLoadParams, QTree, QTable } from "quasar";
@@ -103,6 +116,7 @@ import type {
const emit = defineEmits<{
(event: "lazy-load", callback: LazyLoadCallbackParams): void;
}>();
+const { t } = useI18n();
// props
const props = withDefaults(
@@ -116,7 +130,7 @@ const props = withDefaults(
separator: "unix",
loading: false,
height: "200px",
- }
+ },
);
// expose public methods
@@ -138,21 +152,21 @@ const tableRows = ref([] as FileSystemNodeTable[]);
const tableColumns: QTableColumn[] = [
{
name: "name",
- label: "Name",
+ label: t("dashboard.fileBrowser.columns.name"),
field: "name",
align: "left",
sortable: true,
},
{
name: "type",
- label: "Type",
+ label: t("dashboard.fileBrowser.columns.type"),
field: "type",
align: "left",
sortable: true,
},
{
name: "size",
- label: "Size",
+ label: t("dashboard.fileBrowser.columns.size"),
field: "size",
align: "left",
sortable: true,
@@ -202,7 +216,7 @@ function loadNodeChildren({ node, key, done, fail }: QTreeLazyLoadParams) {
// parses children of node into table rows
function parseNodeChildrenIntoTable(
- node: QTreeFileNode
+ node: QTreeFileNode,
): FileSystemNodeTable[] {
if (isDefined(node.children)) {
return node.children.map((childNode) => ({
diff --git a/src/components/SubTableTabs.vue b/src/components/SubTableTabs.vue
index 0e242f00..f7ad836d 100644
--- a/src/components/SubTableTabs.vue
+++ b/src/components/SubTableTabs.vue
@@ -20,70 +20,70 @@
name="summary"
icon="fas fa-info-circle"
size="xs"
- label="Summary"
+ :label="$t('dashboard.subTableTabs.summary')"
/>
diff --git a/src/components/accounts/PermissionsManager.vue b/src/components/accounts/PermissionsManager.vue
index 351d8c72..a3a975bd 100644
--- a/src/components/accounts/PermissionsManager.vue
+++ b/src/components/accounts/PermissionsManager.vue
@@ -3,7 +3,7 @@
- Manage Roles
+ {{ $t("settings.permissionsManager.title") }}
@@ -21,7 +21,7 @@
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0, sortBy: 'name', descending: false }"
- no-data-label="No Roles"
+ :no-data-label="$t('settings.permissionsManager.noRoles')"
:rows-per-page-options="[0]"
>
@@ -29,7 +29,7 @@
flat
dense
icon="add"
- label="New Role"
+ :label="$t('settings.permissionsManager.newRole')"
@click="showAddRoleModal"
/>
@@ -45,7 +45,9 @@
- Edit
+ {{
+ $t("common.buttons.edit")
+ }}
- Delete
+ {{
+ $t("common.buttons.delete")
+ }}
- Close
+ {{
+ $t("common.buttons.close")
+ }}
@@ -88,35 +94,18 @@
// composition imports
import { ref, onMounted } from "vue";
import { useQuasar, useDialogPluginComponent } from "quasar";
+import { useI18n } from "vue-i18n";
import { fetchRoles, removeRole } from "@/api/accounts";
import { notifySuccess } from "@/utils/notify";
// ui imports
import RolesForm from "@/components/accounts/RolesForm.vue";
-// static data
-const columns = [
- { name: "name", label: "Name", field: "name", align: "left", sortable: true },
- {
- name: "is_superuser",
- label: "Superuser",
- field: "is_superuser",
- align: "left",
- sortable: true,
- },
- {
- name: "user_count",
- label: "Assigned Users",
- field: "user_count",
- align: "left",
- sortable: true,
- },
-];
-
export default {
name: "PermissionsManager",
emits: [...useDialogPluginComponent.emits],
setup() {
+ const { t } = useI18n();
// setup quasar
const $q = useQuasar();
const { dialogRef, onDialogHide } = useDialogPluginComponent();
@@ -124,6 +113,29 @@ export default {
// permission manager logic
const roles = ref([]);
const loading = ref(false);
+ const columns = [
+ {
+ name: "name",
+ label: t("settings.common.name"),
+ field: "name",
+ align: "left",
+ sortable: true,
+ },
+ {
+ name: "is_superuser",
+ label: t("settings.permissionsManager.columns.superuser"),
+ field: "is_superuser",
+ align: "left",
+ sortable: true,
+ },
+ {
+ name: "user_count",
+ label: t("settings.permissionsManager.columns.assignedUsers"),
+ field: "user_count",
+ align: "left",
+ sortable: true,
+ },
+ ];
function showEditRoleModal(role) {
$q.dialog({
@@ -148,9 +160,11 @@ export default {
async function deleteRole(role) {
$q.dialog({
- title: `Delete role ${role.name}?`,
+ title: t("settings.permissionsManager.deleteRoleTitle", {
+ name: role.name,
+ }),
cancel: true,
- ok: { label: "Delete", color: "negative" },
+ ok: { label: t("common.buttons.delete"), color: "negative" },
}).onOk(async () => {
try {
$q.loading.show();
diff --git a/src/components/accounts/ResetPass.vue b/src/components/accounts/ResetPass.vue
index 810247a3..7345ea50 100644
--- a/src/components/accounts/ResetPass.vue
+++ b/src/components/accounts/ResetPass.vue
@@ -2,14 +2,14 @@
- New password:
+ {{ t("auth.resetPassword.newPassword") }}
- Confirm password:
+ {{ t("auth.resetPassword.confirmPassword") }}
-
+
@@ -55,9 +61,12 @@
diff --git a/src/components/modals/agents/RebootLater.vue b/src/components/modals/agents/RebootLater.vue
index f1deb7cb..567155c2 100644
--- a/src/components/modals/agents/RebootLater.vue
+++ b/src/components/modals/agents/RebootLater.vue
@@ -2,31 +2,39 @@
- Schedule reboot on {{ agent.hostname }}
+ {{ $t("agents.rebootLater.title", { hostname: agent.hostname }) }}
- Close
+ {{
+ $t("common.buttons.close")
+ }}
-
+
@@ -39,6 +47,7 @@
// composition imports
import { ref } from "vue";
import { useQuasar, useDialogPluginComponent, date } from "quasar";
+import { useI18n } from "vue-i18n";
import { scheduleAgentReboot } from "@/api/agents";
import { formatDateInputField } from "@/utils/format";
@@ -52,6 +61,7 @@ export default {
// setup quasar dialog plugin
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
const $q = useQuasar();
+ const { t } = useI18n();
// setup reboot later logic
const state = ref({
@@ -68,9 +78,12 @@ export default {
state.value,
);
$q.dialog({
- title: "Reboot pending",
+ title: t("agents.rebootLater.rebootPending"),
style: "width: 40vw",
- message: `A reboot has been scheduled for ${ret.time} on ${props.agent.hostname}. It can be cancelled from the Pending Actions menu until the scheduled time.`,
+ message: t("agents.rebootLater.rebootScheduledMessage", {
+ time: ret.time,
+ hostname: props.agent.hostname,
+ }),
}).onDismiss(onDialogOK);
} catch (e) {
console.error(e);
diff --git a/src/components/modals/agents/RunScript.vue b/src/components/modals/agents/RunScript.vue
index e4c85bd1..76d3373d 100644
--- a/src/components/modals/agents/RunScript.vue
+++ b/src/components/modals/agents/RunScript.vue
@@ -8,7 +8,7 @@
>
- Run a script on {{ agent.hostname }}
+ {{ $t("scripts.runScript.title", { hostname: agent.hostname }) }}
- Minimize
+ {{
+ $t("scripts.runScript.minimize")
+ }}
- Maximize
+ {{
+ $t("scripts.runScript.maximize")
+ }}
- Close
+ {{
+ $t("scripts.shared.close")
+ }}
@@ -119,7 +121,7 @@
>
-
+
- {{ runAsUserToolTip }}
+ {{
+ $t("scripts.runScript.runAsUserTooltip")
+ }}
- Enable server side scripts globally to activate this
- feature.
- Run the script on the Tactical RMM server in the context of this
- agent.
+ {{
+ $t("scripts.runScript.enableServerScriptsHint")
+ }}
+ {{
+ $t("scripts.runScript.runOnServerHint")
+ }}
@@ -172,20 +177,21 @@
outlined
type="number"
style="max-width: 150px"
- label="Timeout (seconds)"
+ :label="$t('scripts.runScript.timeoutSeconds')"
stack-label
:rules="[
- (val) => !!val || '*Required',
- (val) => val >= 5 || 'Minimum is 5 seconds',
+ (val) => !!val || $t('scripts.shared.required'),
+ (val) =>
+ val >= 5 || $t('scripts.runScript.minimumSeconds', { min: 5 }),
]"
/>
-
+
@@ -197,23 +203,27 @@
>
{{ ret }}
- Run Time:
- {{ ret.execution_time }} seconds
-
Return Code:
+ {{ $t("scripts.runScript.runTime") }}:
+ {{
+ $t("scripts.runScript.secondsValue", {
+ value: ret.execution_time,
+ })
+ }}
+
{{ $t("scripts.runScript.returnCode") }}:
{{ ret.retcode }}
@@ -221,7 +231,7 @@
@@ -239,11 +249,11 @@
import { computed, ref, watch } from "vue";
import { useStore } from "vuex";
import { useDialogPluginComponent, openURL } from "quasar";
+import { useI18n } from "vue-i18n";
import { useScriptDropdown } from "@/composables/scripts";
import { useCustomFieldDropdown } from "@/composables/core";
import { runScript } from "@/api/agents";
import { notifySuccess } from "@/utils/notify";
-import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
//ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
@@ -259,15 +269,6 @@ const server_scripts_enabled = computed(
() => store.state.server_scripts_enabled,
);
-// static data
-const outputOptions = [
- { label: "Wait for Output", value: "wait" },
- { label: "Fire and Forget", value: "forget" },
- { label: "Email results", value: "email" },
- { label: "Save results to Custom Field", value: "collector" },
- { label: "Save results to Agent Notes", value: "note" },
-];
-
// emits
defineEmits([...useDialogPluginComponent.emits]);
@@ -279,6 +280,7 @@ const props = defineProps<{
// setup quasar dialog plugin
const { dialogRef, onDialogHide } = useDialogPluginComponent();
+const { t } = useI18n();
// setup dropdowns
const {
@@ -315,6 +317,17 @@ const ret = ref(null);
const loading = ref(false);
const maximized = ref(false);
+const outputOptions = computed(() => [
+ { label: t("scripts.runScript.outputOptions.wait"), value: "wait" },
+ { label: t("scripts.runScript.outputOptions.forget"), value: "forget" },
+ { label: t("scripts.runScript.outputOptions.email"), value: "email" },
+ {
+ label: t("scripts.runScript.outputOptions.collector"),
+ value: "collector",
+ },
+ { label: t("scripts.runScript.outputOptions.note"), value: "note" },
+]);
+
async function sendScript() {
ret.value = null;
loading.value = true;
diff --git a/src/components/modals/agents/SendCommand.vue b/src/components/modals/agents/SendCommand.vue
index 476854e8..ed727585 100644
--- a/src/components/modals/agents/SendCommand.vue
+++ b/src/components/modals/agents/SendCommand.vue
@@ -10,22 +10,24 @@
:style="{ 'min-width': ret || streamOutput ? '70vw' : '40vw' }"
>
- Send command on {{ agent.hostname }}
+ {{ $t("agents.sendCommand.title", { hostname: agent.hostname }) }}
- Close
+ {{
+ $t("common.buttons.close")
+ }}
- Shell
+ {{ $t("agents.sendCommand.shell") }}
-
- {{ runAsUserToolTip }}
+
+ {{
+ $t("agents.sendCommand.runAsUserTooltip")
+ }}
@@ -73,12 +80,16 @@
outlined
type="number"
style="max-width: 150px"
- label="Timeout (seconds)"
+ :label="$t('agents.sendCommand.timeoutSeconds')"
stack-label
:rules="[
- (val) => !!val || '*Required',
- (val) => val >= 10 || 'Minimum is 10 seconds',
- (val) => val <= 3600 || 'Maximum is 3600 seconds',
+ (val) => !!val || $t('agents.shared.required'),
+ (val) =>
+ val >= 10 ||
+ $t('agents.sendCommand.minimumSeconds', { min: 10 }),
+ (val) =>
+ val <= 3600 ||
+ $t('agents.sendCommand.maximumSeconds', { max: 3600 }),
]"
/>
@@ -86,29 +97,41 @@
-
+
-
+
+
- Update Agents
+ {{ $t("agents.updateAgents.title") }}
- Close
+ {{
+ $t("common.buttons.close")
+ }}
@@ -12,12 +14,10 @@
- If agent auto update is enabled in Global Settings, agents will
- automatically self update at 35 min past the hour, every hour. Use this
- tool to manually trigger an agent update cycle.
+ {{ $t("agents.updateAgents.banner") }}
- Select Version
+ {{ $t("agents.updateAgents.selectVersion") }}
- Select Agent
+ {{ $t("agents.updateAgents.selectAgent") }}
{
this.$emit("close");
- this.notifySuccess("Agents will now be updated");
+ this.notifySuccess(this.$t("agents.updateAgents.agentsWillBeUpdated"));
});
},
},
diff --git a/src/components/modals/agents/WebsocketSendCommand.vue b/src/components/modals/agents/WebsocketSendCommand.vue
index 21845e18..a4297449 100644
--- a/src/components/modals/agents/WebsocketSendCommand.vue
+++ b/src/components/modals/agents/WebsocketSendCommand.vue
@@ -10,26 +10,34 @@
:style="{ 'min-width': !ret ? '40vw' : '70vw' }"
>
- Send command on {{ agent.hostname }}
+ {{
+ $t("agents.websocketSendCommand.title", { hostname: agent.hostname })
+ }}
- Websocket diconnected! {{ $t("agents.websocketSendCommand.websocketDisconnected") }}
- Close
+ {{
+ $t("common.buttons.close")
+ }}
- Shell
+ {{ $t("agents.websocketSendCommand.shell") }}
@@ -59,10 +67,10 @@
@@ -72,12 +80,16 @@
outlined
type="number"
style="max-width: 150px"
- label="Timeout (seconds)"
+ :label="$t('agents.websocketSendCommand.timeoutSeconds')"
stack-label
:rules="[
- (val) => !!val || '*Required',
- (val) => val >= 10 || 'Minimum is 10 seconds',
- (val) => val <= 3600 || 'Maximum is 3600 seconds',
+ (val) => !!val || $t('agents.shared.required'),
+ (val) =>
+ val >= 10 ||
+ $t('agents.websocketSendCommand.minimumSeconds', { min: 10 }),
+ (val) =>
+ val <= 3600 ||
+ $t('agents.websocketSendCommand.maximumSeconds', { max: 3600 }),
]"
/>
@@ -85,21 +97,27 @@
-
+
diff --git a/src/components/modals/alerts/AlertExclusions.vue b/src/components/modals/alerts/AlertExclusions.vue
index bf9597a9..83683562 100644
--- a/src/components/modals/alerts/AlertExclusions.vue
+++ b/src/components/modals/alerts/AlertExclusions.vue
@@ -2,16 +2,18 @@
- Alert Exclusions for {{ template.name }}
+ {{ $t("alerts.exclusions.title", { name: template.name }) }}
- Close
+ {{
+ $t("alerts.common.close")
+ }}
-
-
+
+
@@ -99,7 +107,7 @@ export default {
.then(() => {
this.$q.loading.hide();
this.onOk();
- this.notifySuccess("Alert Template exclusions added");
+ this.notifySuccess(this.$t("alerts.exclusions.notify.saved"));
})
.catch(() => {
this.$q.loading.hide();
@@ -118,7 +126,7 @@ export default {
r.data.forEach((client) => {
this.siteOptions.push({ category: client.name });
client.sites.forEach((site) =>
- this.siteOptions.push({ label: site.name, value: site.id })
+ this.siteOptions.push({ label: site.name, value: site.id }),
);
});
this.$q.loading.hide();
@@ -129,7 +137,7 @@ export default {
},
getOptions() {
this.getAgentOptions("id").then(
- (options) => (this.agentOptions = Object.freeze(options))
+ (options) => (this.agentOptions = Object.freeze(options)),
);
this.getClientsandSites();
},
diff --git a/src/components/modals/alerts/AlertTemplateAdd.vue b/src/components/modals/alerts/AlertTemplateAdd.vue
index d53ba997..d16a7fcb 100644
--- a/src/components/modals/alerts/AlertTemplateAdd.vue
+++ b/src/components/modals/alerts/AlertTemplateAdd.vue
@@ -2,10 +2,12 @@
- Edit Alert Template assigned to {{ type }}
+ {{ $t("alerts.templateAdd.title", { type: capitalize(type) }) }}
- Close
+ {{
+ $t("alerts.common.close")
+ }}
@@ -18,19 +20,21 @@
clearable
emit-value
map-options
- :label="capitalize(type) + ' Alert Template'"
+ :label="
+ $t('alerts.templateAdd.selectLabel', { type: capitalize(type) })
+ "
>
- No Alert Templates have been setup. Go to Settings > Alerts Manager
+ {{ $t("alerts.templateAdd.noTemplates") }}
-
+
@@ -91,13 +95,19 @@ export default {
data = { id: this.object.id, alert_template: this.selectedTemplate };
}
- const text = this.selectedTemplate ? "assigned" : "removed";
+ const text = this.selectedTemplate
+ ? this.$t("alerts.templateAdd.actions.assigned")
+ : this.$t("alerts.templateAdd.actions.removed");
this.$axios
.put(url, data)
.then(() => {
this.$q.loading.hide();
this.onOk();
- this.notifySuccess(`Alert Template ${text} successfully!`);
+ this.notifySuccess(
+ this.$t("alerts.templateAdd.notify.templateUpdated", {
+ action: text,
+ }),
+ );
})
.catch(() => {
this.$q.loading.hide();
diff --git a/src/components/modals/alerts/AlertTemplateForm.vue b/src/components/modals/alerts/AlertTemplateForm.vue
index ce3c4849..4fa62980 100644
--- a/src/components/modals/alerts/AlertTemplateForm.vue
+++ b/src/components/modals/alerts/AlertTemplateForm.vue
@@ -2,10 +2,16 @@
- {{ alertTemplate ? "Edit Alert Template" : "Add Alert Template" }}
+ {{
+ alertTemplate
+ ? t("alerts.templateForm.dialog.editTitle")
+ : t("alerts.templateForm.dialog.addTitle")
+ }}
- Close
+ {{
+ t("alerts.common.close")
+ }}
@@ -38,18 +44,18 @@
- Email Settings (Overrides global email settings)
+ {{ t("alerts.templateForm.sections.emailSettings") }}
- Email recipients
+
+ {{ t("alerts.templateForm.fields.emailRecipients") }}
+
- No recipients
+ {{
+ t("alerts.templateForm.noRecipients")
+ }}
@@ -91,18 +101,20 @@
size="sm"
icon="fas fa-plus"
color="secondary"
- label="Add email"
+ :label="t('alerts.templateForm.actions.addEmail')"
@click="toggleAddEmail"
/>
- SMS Settings (Overrides global SMS settings)
+ {{ t("alerts.templateForm.sections.smsSettings") }}
- SMS recipients
+
+ {{ t("alerts.templateForm.fields.smsRecipients") }}
+
- No recipients
+ {{
+ t("alerts.templateForm.noRecipients")
+ }}
@@ -136,7 +150,7 @@
size="sm"
icon="fas fa-plus"
color="secondary"
- label="Add sms number"
+ :label="t('alerts.templateForm.actions.addSmsNumber')"
@click="toggleAddSMSNumber"
/>
@@ -144,13 +158,17 @@
-
+
Alert Failure Settings
+ >{{ t("alerts.templateForm.sections.alertFailureSettings") }}
- The selected action will run when an alert is triggered.
+ {{ t("alerts.templateForm.tooltips.failureActionRuns") }}
@@ -167,20 +185,20 @@
Alert Resolved Settings
+ >{{ t("alerts.templateForm.sections.alertResolvedSettings") }}
- The selected action will run when an alert is resolved.
+ {{ t("alerts.templateForm.tooltips.resolvedActionRuns") }}
@@ -266,7 +286,7 @@
Run actions only on
+ >{{ t("alerts.templateForm.sections.runActionsOnlyOn") }}
- The selected action will only run on the following types of
- alerts
+ {{ t("alerts.templateForm.tooltips.runActionsOnlyOn") }}
@@ -356,21 +377,21 @@
@@ -378,38 +399,38 @@
-
+
Alert Failure Settings
+ >{{ t("alerts.templateForm.sections.alertFailureSettings") }}
- Select what notifications should be sent when an agent is
- overdue. Enabled will override the agent notification settings
- and always notify. Not configured will use what notification
- settings are configured on the agent. Disabled will override
- the agent notification settings and never notify.
+ {{ t("alerts.templateForm.tooltips.agentOverdueFailure") }}
Alert Resolved Settings
+ >{{ t("alerts.templateForm.sections.alertResolvedSettings") }}
- Select what notifications should be sent when an overdue agent
- is back online.
+ {{ t("alerts.templateForm.tooltips.agentOverdueResolved") }}
@@ -454,17 +476,17 @@
-
+
Alert Failure Settings
+ >{{ t("alerts.templateForm.sections.alertFailureSettings") }}
- Select what notifications are sent when a check fails. Enabled
- will override the check notification settings and always
- notify. Not configured will use the notification settings
- configured on the check. Disabled will override the check
- notification settings and never notify.
+ {{ t("alerts.templateForm.tooltips.checkFailure") }}
@@ -472,21 +494,21 @@
Alert Resolved Settings
+ >{{ t("alerts.templateForm.sections.alertResolvedSettings") }}
- Select what notifications are sent when a failed check is
- resolved.
+ {{ t("alerts.templateForm.tooltips.checkResolved") }}
@@ -580,17 +603,17 @@
-
+
Alert Failure Settings
+ >{{ t("alerts.templateForm.sections.alertFailureSettings") }}
- Select what notifications are sent when an automated task
- fails. Enabled will override the task notification settings
- and always notify. Not configured will use the notification
- settings configured on the task. Disabled will override the
- task notification settings and never notify.
+ {{ t("alerts.templateForm.tooltips.taskFailure") }}
@@ -598,21 +621,21 @@
Alert Resolved Settings
+ >{{ t("alerts.templateForm.sections.alertResolvedSettings") }}
- Select what notifications are sent when a failed task is
- resolved.
+ {{ t("alerts.templateForm.tooltips.taskResolved") }}
@@ -712,20 +738,20 @@
flat
color="primary"
@click="stepper?.previous()"
- label="Back"
+ :label="t('alerts.templateForm.actions.back')"
class="q-mr-xs"
/>
@@ -739,6 +765,7 @@
import { computed, ref, reactive, watch, nextTick } from "vue";
import { useStore } from "vuex";
import { useQuasar, useDialogPluginComponent, type QStepper } from "quasar";
+import { useI18n } from "vue-i18n";
import { useScriptDropdown } from "@/composables/scripts";
import { useURLActionDropdown } from "@/composables/core";
import { notifyError, notifySuccess } from "@/utils/notify";
@@ -769,6 +796,7 @@ defineEmits([...useDialogPluginComponent.emits]);
// setup quasar plugins
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
const $q = useQuasar();
+const { t } = useI18n();
const step = ref(1);
@@ -906,15 +934,15 @@ if (props.alertTemplate) {
}
const severityOptions = [
- { label: "Error", value: "error" },
- { label: "Warning", value: "warning" },
- { label: "Informational", value: "info" },
+ { label: t("alerts.templateForm.severity.error"), value: "error" },
+ { label: t("alerts.templateForm.severity.warning"), value: "warning" },
+ { label: t("alerts.templateForm.severity.informational"), value: "info" },
];
const staticActionTypeOptions = [
- { label: "Send a Web Hook", value: "rest" },
- { label: "Run script on Agent", value: "script" },
- { label: "Run script on TRMM Server", value: "server" },
+ { label: t("alerts.templateForm.actionTypes.webhook"), value: "rest" },
+ { label: t("alerts.templateForm.actionTypes.agentScript"), value: "script" },
+ { label: t("alerts.templateForm.actionTypes.serverScript"), value: "server" },
];
const actionTypeOptions = computed(() => {
@@ -938,14 +966,14 @@ const actionTypeOptions = computed(() => {
const stepper = ref(null);
function toggleAddEmail() {
$q.dialog({
- title: "Add email",
prompt: {
model: "",
isValid: (val) => isValidEmail(val),
type: "email",
},
cancel: true,
- ok: { label: "Add", color: "primary" },
+ title: t("alerts.templateForm.dialog.addEmailTitle"),
+ ok: { label: t("alerts.templateForm.actions.add"), color: "primary" },
persistent: false,
}).onOk((data) => {
template.email_recipients.push(data);
@@ -954,15 +982,14 @@ function toggleAddEmail() {
function toggleAddSMSNumber() {
$q.dialog({
- title: "Add number",
- message:
- "Use E.164 format: must have the + symbol and country code, followed by the phone number e.g. +12131231234",
+ title: t("alerts.templateForm.dialog.addNumberTitle"),
+ message: t("alerts.templateForm.dialog.e164Message"),
prompt: {
model: "",
},
html: true,
cancel: true,
- ok: { label: "Add", color: "primary" },
+ ok: { label: t("alerts.templateForm.actions.add"), color: "primary" },
persistent: false,
}).onOk((data: string) => {
template.text_recipients.push(data);
@@ -984,7 +1011,7 @@ const loading = ref(false);
async function onSubmit() {
// TODO rework this ghetto form validation
if (!template.name) {
- notifyError("Name needs to be set");
+ notifyError(t("alerts.templateForm.notify.nameRequired"));
return;
}
@@ -993,7 +1020,7 @@ async function onSubmit() {
if (props.alertTemplate) {
try {
await saveAlertTemplate(template.id, template);
- notifySuccess("Alert Template edited!");
+ notifySuccess(t("alerts.templateForm.notify.edited"));
onDialogOK();
} catch {
} finally {
@@ -1002,7 +1029,7 @@ async function onSubmit() {
} else {
try {
await addAlertTemplate(template);
- notifySuccess("Alert Template edited!");
+ notifySuccess(t("alerts.templateForm.notify.created"));
onDialogOK();
} catch {
} finally {
diff --git a/src/components/modals/alerts/AlertTemplateRelated.vue b/src/components/modals/alerts/AlertTemplateRelated.vue
index 74f38a93..3b23e3c0 100644
--- a/src/components/modals/alerts/AlertTemplateRelated.vue
+++ b/src/components/modals/alerts/AlertTemplateRelated.vue
@@ -2,10 +2,12 @@
- Assigned to {{ template.name }}
+ {{ $t("alerts.templateRelated.assignedTo", { name: template.name }) }}
- Close
+ {{
+ $t("alerts.common.close")
+ }}
@@ -20,9 +22,18 @@
narrow-indicator
no-caps
>
-
-
-
+
+
+
diff --git a/src/components/modals/alerts/AlertsOverview.vue b/src/components/modals/alerts/AlertsOverview.vue
index 806dff80..64bca491 100644
--- a/src/components/modals/alerts/AlertsOverview.vue
+++ b/src/components/modals/alerts/AlertsOverview.vue
@@ -10,20 +10,24 @@
- Alerts Overview
+ {{ $t("alerts.overview.title") }}
- Close
+ {{
+ $t("alerts.common.close")
+ }}
- Filter
+
+ {{ $t("alerts.overview.filter") }}
+
-
+
@@ -98,11 +106,13 @@
virtual-scroll
>
- Alerts
+
+ {{ $t("alerts.overview.alerts") }}
+
@@ -115,7 +125,9 @@
- Snooze alerts
+ {{
+ $t("alerts.overview.snoozeAlerts")
+ }}
- Resolve alerts
+ {{
+ $t("alerts.overview.resolveAlerts")
+ }}
@@ -143,7 +157,9 @@
class="cursor-pointer"
@click="showScriptOutput(props.row, true)"
>
- Show failure action run results
+ {{
+ $t("alerts.overview.tooltips.showFailureActionResults")
+ }}
- Show resolved action run results
+ {{
+ $t("alerts.overview.tooltips.showResolvedActionResults")
+ }}
- Snooze alert
+ {{ $t("alerts.common.snoozeAlert") }}
- Unsnooze alert
+ {{
+ $t("alerts.overview.tooltips.unsnoozeAlert")
+ }}
- Resolve alert
+ {{ $t("alerts.common.resolveAlert") }}
@@ -227,23 +247,35 @@ export default {
searched: false,
clientsOptions: [],
severityOptions: [
- { label: "Informational", value: "info" },
- { label: "Warning", value: "warning" },
- { label: "Error", value: "error" },
+ {
+ label: this.$t("alerts.overview.severityOptions.informational"),
+ value: "info",
+ },
+ {
+ label: this.$t("alerts.overview.severityOptions.warning"),
+ value: "warning",
+ },
+ {
+ label: this.$t("alerts.overview.severityOptions.error"),
+ value: "error",
+ },
],
timeOptions: [
- { value: 1, label: "1 Day Ago" },
- { value: 7, label: "1 Week Ago" },
- { value: 30, label: "30 Days Ago" },
- { value: 90, label: "3 Months Ago" },
- { value: 180, label: "6 Months Ago" },
- { value: 365, label: "1 Year Ago" },
- { value: 0, label: "Everything" },
+ { value: 1, label: this.$t("alerts.overview.timeOptions.dayAgo") },
+ { value: 7, label: this.$t("alerts.overview.timeOptions.weekAgo") },
+ { value: 30, label: this.$t("alerts.overview.timeOptions.days30Ago") },
+ { value: 90, label: this.$t("alerts.overview.timeOptions.months3Ago") },
+ {
+ value: 180,
+ label: this.$t("alerts.overview.timeOptions.months6Ago"),
+ },
+ { value: 365, label: this.$t("alerts.overview.timeOptions.yearAgo") },
+ { value: 0, label: this.$t("alerts.overview.timeOptions.everything") },
],
columns: [
{
name: "alert_time",
- label: "Time",
+ label: this.$t("alerts.overview.columns.time"),
field: "alert_time",
align: "left",
sortable: true,
@@ -251,28 +283,28 @@ export default {
},
{
name: "client",
- label: "Client",
+ label: this.$t("alerts.overview.columns.client"),
field: "client",
align: "left",
sortable: true,
},
{
name: "site",
- label: "Site",
+ label: this.$t("alerts.overview.columns.site"),
field: "site",
align: "left",
sortable: true,
},
{
name: "hostname",
- label: "Agent",
+ label: this.$t("alerts.overview.columns.agent"),
field: "hostname",
align: "left",
sortable: true,
},
{
name: "alert_type",
- label: "Type",
+ label: this.$t("alerts.overview.columns.type"),
field: "alert_type",
align: "left",
sortable: true,
@@ -280,21 +312,21 @@ export default {
},
{
name: "severity",
- label: "Severity",
+ label: this.$t("alerts.overview.columns.severity"),
field: "severity",
align: "left",
sortable: true,
},
{
name: "message",
- label: "Message",
+ label: this.$t("alerts.overview.columns.message"),
field: "message",
align: "left",
sortable: true,
},
{
name: "resolved_on",
- label: "Resolved On",
+ label: this.$t("alerts.overview.columns.resolvedOn"),
field: "resolved_on",
align: "left",
sortable: true,
@@ -302,13 +334,17 @@ export default {
},
{
name: "snoozed_until",
- label: "Snoozed Until",
+ label: this.$t("alerts.overview.columns.snoozedUntil"),
field: "snoozed_until",
align: "left",
sortable: true,
format: (a) => this.formatDate(a),
},
- { name: "actions", label: "Actions", align: "left" },
+ {
+ name: "actions",
+ label: this.$t("alerts.overview.columns.actions"),
+ align: "left",
+ },
],
pagination: {
rowsPerPage: 50,
@@ -320,8 +356,8 @@ export default {
computed: {
noDataText() {
return this.searched
- ? "No data found. Try to refine you search"
- : "Click search to find alerts";
+ ? this.$t("alerts.overview.noData.searched")
+ : this.$t("alerts.overview.noData.initial");
},
visibleColumns() {
return this.columns.map((column) => {
@@ -373,8 +409,8 @@ export default {
snoozeAlert(alert) {
this.$q
.dialog({
- title: "Snooze Alert",
- message: "How many days to snooze alert?",
+ title: this.$t("alerts.dialog.snoozeTitle"),
+ message: this.$t("alerts.dialog.snoozeMessage"),
prompt: {
model: "",
type: "number",
@@ -396,7 +432,9 @@ export default {
.then(() => {
this.search();
this.$q.loading.hide();
- this.notifySuccess(`The alert has been snoozed for ${days} days`);
+ this.notifySuccess(
+ this.$t("alerts.notify.snoozedForDays", { days: days }),
+ );
})
.catch(() => {
this.$q.loading.hide();
@@ -416,7 +454,7 @@ export default {
.then(() => {
this.search();
this.$q.loading.hide();
- this.notifySuccess("The alert has been unsnoozed");
+ this.notifySuccess(this.$t("alerts.notify.unsnoozed"));
})
.catch(() => {
this.$q.loading.hide();
@@ -435,7 +473,7 @@ export default {
.then(() => {
this.search();
this.$q.loading.hide();
- this.notifySuccess("The alert has been resolved");
+ this.notifySuccess(this.$t("alerts.notify.resolved"));
})
.catch(() => {
this.$q.loading.hide();
@@ -454,7 +492,7 @@ export default {
.then(() => {
this.search();
this.$q.loading.hide();
- this.notifySuccess("Alerts were resolved");
+ this.notifySuccess(this.$t("alerts.notify.bulkResolved"));
})
.catch(() => {
this.$q.loading.hide();
@@ -463,8 +501,8 @@ export default {
snoozeAlertBulk(alerts) {
this.$q
.dialog({
- title: "Snooze Alert",
- message: "How many days to snooze alert?",
+ title: this.$t("alerts.dialog.snoozeTitle"),
+ message: this.$t("alerts.dialog.snoozeMessage"),
prompt: {
model: "",
type: "number",
@@ -486,7 +524,9 @@ export default {
.then(() => {
this.search();
this.$q.loading.hide();
- this.notifySuccess(`Alerts were snoozed for ${days} days`);
+ this.notifySuccess(
+ this.$t("alerts.notify.bulkSnoozedForDays", { days: days }),
+ );
})
.catch(() => {
this.$q.loading.hide();
@@ -496,14 +536,20 @@ export default {
showScriptOutput(alert, failure = false) {
let results = {};
if (failure) {
- results.readable_desc = `${alert.alert_type} failure action results`;
+ results.readable_desc = this.$t(
+ "alerts.overview.actionResults.failure",
+ { type: alert.alert_type },
+ );
results.execution_time = alert.action_execution_time;
results.retcode = alert.action_retcode;
results.stdout = alert.action_stdout;
results.errout = alert.action_errout;
results.last_run = alert.action_run;
} else {
- results.readable_desc = `${alert.alert_type} resolved action results`;
+ results.readable_desc = this.$t(
+ "alerts.overview.actionResults.resolved",
+ { type: alert.alert_type },
+ );
results.execution_time = alert.resolved_action_execution_time;
results.retcode = alert.resolved_action_retcode;
results.stdout = alert.resolved_action_stdout;
diff --git a/src/components/modals/core/ServerMaintenance.vue b/src/components/modals/core/ServerMaintenance.vue
index 2b7f6818..863ee43e 100644
--- a/src/components/modals/core/ServerMaintenance.vue
+++ b/src/components/modals/core/ServerMaintenance.vue
@@ -2,7 +2,7 @@
- Server Maintenance
+ {{ $t("settings.serverMaintenance.title") }}
@@ -13,10 +13,10 @@
-
- Removes agent check results
+
+ {{
+ $t("settings.serverMaintenance.auditLogTooltip")
+ }}
- Removes completed pending actions
+ {{
+ $t("settings.serverMaintenance.pendingActionsTooltip")
+ }}
-
- Removes all alerts
+
+ {{
+ $t("settings.serverMaintenance.alertsTooltip")
+ }}
- Code Signing
+ {{ t("settings.codeSign.title") }}
- Force all existing agents to be updated to the code-signed
- version
+ {{
+ t("settings.codeSign.codeSignAllAgentsTooltip")
+ }}
@@ -25,20 +24,28 @@
- Token:
+ {{ t("settings.codeSign.token") }}:
-
+
-
+
@@ -49,6 +56,7 @@ import { ref, onMounted } from "vue";
import { useQuasar } from "quasar";
import axios from "axios";
import { notifySuccess } from "@/utils/notify";
+import { useI18n } from "vue-i18n";
const endpoint = "/core/codesign/";
@@ -56,6 +64,7 @@ export default {
name: "CodeSign",
setup() {
const $q = useQuasar();
+ const { t } = useI18n();
const settings = ref({ token: "" });
const loading = ref(false);
@@ -71,7 +80,7 @@ export default {
async function deleteToken() {
try {
await axios.delete(endpoint);
- notifySuccess("Token was deleted!");
+ notifySuccess(t("settings.codeSign.notify.tokenDeleted"));
await getToken();
} catch (e) {
console.error(e);
@@ -80,7 +89,7 @@ export default {
function confirmDelete() {
$q.dialog({
- title: "Delete token?",
+ title: t("settings.codeSign.deleteTokenTitle"),
cancel: true,
persistent: true,
}).onOk(() => {
@@ -119,6 +128,7 @@ export default {
return {
settings,
loading,
+ t,
confirmDelete,
doCodeSign,
editToken,
diff --git a/src/components/modals/coresettings/CustomFields.vue b/src/components/modals/coresettings/CustomFields.vue
index 28382cc4..7a6db6fd 100644
--- a/src/components/modals/coresettings/CustomFields.vue
+++ b/src/components/modals/coresettings/CustomFields.vue
@@ -1,14 +1,14 @@
- Custom Fields
+ {{ $t("settings.customFields.title") }}
@@ -25,9 +25,9 @@
narrow-indicator
no-caps
>
-
-
-
+
+
+
diff --git a/src/components/modals/coresettings/CustomFieldsForm.vue b/src/components/modals/coresettings/CustomFieldsForm.vue
index 4b1d82cd..e3b6fa6e 100644
--- a/src/components/modals/coresettings/CustomFieldsForm.vue
+++ b/src/components/modals/coresettings/CustomFieldsForm.vue
@@ -5,14 +5,16 @@
{{ title }}
- Close
+ {{
+ $t("settings.common.close")
+ }}
@@ -54,7 +56,7 @@
>
@@ -95,7 +97,7 @@
-
-
+
+
@@ -180,23 +187,43 @@ export default {
hide_in_summary: false,
},
modelOptions: [
- { label: "Client", value: "client" },
- { label: "Site", value: "site" },
- { label: "Agent", value: "agent" },
+ { label: this.$t("settings.customFields.clients"), value: "client" },
+ { label: this.$t("settings.customFields.sites"), value: "site" },
+ { label: this.$t("settings.customFields.agents"), value: "agent" },
],
typeOptions: [
- { label: "Text", value: "text" },
- { label: "Number", value: "number" },
- { label: "Dropdown Single", value: "single" },
- { label: "Dropdown Multiple", value: "multiple" },
- { label: "DateTime", value: "datetime" },
- { label: "Checkbox", value: "checkbox" },
+ {
+ label: this.$t("settings.customFieldsForm.type.text"),
+ value: "text",
+ },
+ {
+ label: this.$t("settings.customFieldsForm.type.number"),
+ value: "number",
+ },
+ {
+ label: this.$t("settings.customFieldsForm.type.dropdownSingle"),
+ value: "single",
+ },
+ {
+ label: this.$t("settings.customFieldsForm.type.dropdownMultiple"),
+ value: "multiple",
+ },
+ {
+ label: this.$t("settings.customFieldsForm.type.datetime"),
+ value: "datetime",
+ },
+ {
+ label: this.$t("settings.customFieldsForm.type.checkbox"),
+ value: "checkbox",
+ },
],
};
},
computed: {
title() {
- return this.editing ? "Edit Custom Field" : "Add Custom Field";
+ return this.editing
+ ? this.$t("settings.customFieldsForm.editTitle")
+ : this.$t("settings.customFieldsForm.addTitle");
},
editing() {
return !!this.field;
@@ -204,7 +231,8 @@ export default {
defaultValueRules() {
if (this.localField.required) {
return [
- (val) => !!val || "Default Value needs to be set for required fields",
+ (val) =>
+ !!val || this.$t("settings.customFieldsForm.defaultValueRequired"),
];
} else {
return [];
@@ -225,7 +253,9 @@ export default {
.then(() => {
this.$q.loading.hide();
this.onOk();
- this.notifySuccess("Custom field edited!");
+ this.notifySuccess(
+ this.$t("settings.customFieldsForm.notify.edited"),
+ );
})
.catch(() => {
this.$q.loading.hide();
@@ -236,7 +266,9 @@ export default {
.then(() => {
this.$q.loading.hide();
this.onOk();
- this.notifySuccess("Custom field added!");
+ this.notifySuccess(
+ this.$t("settings.customFieldsForm.notify.added"),
+ );
})
.catch(() => {
this.$q.loading.hide();
diff --git a/src/components/modals/coresettings/CustomFieldsTable.vue b/src/components/modals/coresettings/CustomFieldsTable.vue
index 87dd1359..6e5afe15 100644
--- a/src/components/modals/coresettings/CustomFieldsTable.vue
+++ b/src/components/modals/coresettings/CustomFieldsTable.vue
@@ -9,7 +9,7 @@
hide-pagination
virtual-scroll
:rows-per-page-options="[0]"
- no-data-label="No Custom Fields"
+ :no-data-label="$t('settings.customFieldsTable.noData')"
>
@@ -25,7 +25,7 @@
- Edit
+ {{ $t("settings.common.edit") }}
- Delete
+ {{
+ $t("settings.common.delete")
+ }}
- Close
+ {{ $t("settings.common.close") }}
{{ props.row.name }}
- ID: {{ props.row.id }}
+ {{
+ $t("settings.customFieldsTable.id", { id: props.row.id })
+ }}
@@ -109,42 +113,42 @@ export default {
columns: [
{
name: "name",
- label: "Name",
+ label: this.$t("settings.common.name"),
field: "name",
align: "left",
sortable: true,
},
{
name: "type",
- label: "Field Type",
+ label: this.$t("settings.customFieldsTable.columns.fieldType"),
field: "type",
align: "left",
sortable: true,
},
{
name: "hide_in_ui",
- label: "Hide in UI",
+ label: this.$t("settings.customFieldsTable.columns.hideInUi"),
field: "hide_in_ui",
align: "left",
sortable: true,
},
{
name: "hide_in_summary",
- label: "Hide in Summary Tab",
+ label: this.$t("settings.customFieldsTable.columns.hideInSummary"),
field: "hide_in_summary",
align: "left",
sortable: true,
},
{
name: "default_value",
- label: "Default Value",
+ label: this.$t("settings.customFieldsTable.columns.defaultValue"),
field: "default_value",
align: "left",
sortable: true,
},
{
name: "required",
- label: "Required",
+ label: this.$t("settings.common.required"),
field: "required",
align: "left",
sortable: true,
@@ -168,9 +172,11 @@ export default {
deleteCustomField(field) {
this.$q
.dialog({
- title: `Delete custom field ${field.name}?`,
+ title: this.$t("settings.customFieldsTable.deleteTitle", {
+ name: field.name,
+ }),
cancel: true,
- ok: { label: "Delete", color: "negative" },
+ ok: { label: this.$t("settings.common.delete"), color: "negative" },
})
.onOk(() => {
this.$q.loading.show();
@@ -179,7 +185,11 @@ export default {
.then(() => {
this.refresh();
this.$q.loading.hide();
- this.notifySuccess(`Custom Field ${field.name} was deleted!`);
+ this.notifySuccess(
+ this.$t("settings.customFieldsTable.notify.deleted", {
+ name: field.name,
+ }),
+ );
})
.catch(() => {
this.$q.loading.hide();
diff --git a/src/components/modals/coresettings/EditCoreSettings.vue b/src/components/modals/coresettings/EditCoreSettings.vue
index b6272d06..cfe99560 100644
--- a/src/components/modals/coresettings/EditCoreSettings.vue
+++ b/src/components/modals/coresettings/EditCoreSettings.vue
@@ -3,25 +3,60 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
- Global Settings
+
+ {{ $t("settings.editCoreSettings.title") }}
+
@@ -34,25 +69,42 @@
>
- General
+
+ {{ $t("settings.editCoreSettings.tabs.general") }}
+
- Runs at 35mins past every hour
+
+ {{
+ $t(
+ "settings.editCoreSettings.general.enableAgentAutoUpdateTooltip",
+ )
+ }}
+
- Allow running scripts on TRMM server for alert
- failure/resolve actions
+ {{
+ $t(
+ "settings.editCoreSettings.general.enableServerSideScriptsTooltip",
+ )
+ }}
- Enable the web terminal
+ {{
+ $t(
+ "settings.editCoreSettings.general.enableWebTerminalTooltip",
+ )
+ }}
- Default agent timezone:
+
+ {{
+ $t(
+ "settings.editCoreSettings.general.defaultAgentTimezone",
+ )
+ }}:
+
- Default date format:
+
+ {{
+ $t("settings.editCoreSettings.general.defaultDateFormat")
+ }}:
+
- Click to see formatting options
+ {{
+ $t(
+ "settings.editCoreSettings.general.clickFormattingOptions",
+ )
+ }}
- Default server policy:
+
+ {{
+ $t(
+ "settings.editCoreSettings.general.defaultServerPolicy",
+ )
+ }}:
+
- Default workstation policy:
+
+ {{
+ $t(
+ "settings.editCoreSettings.general.defaultWorkstationPolicy",
+ )
+ }}:
+
- Default alert template:
+
+ {{
+ $t(
+ "settings.editCoreSettings.general.defaultAlertTemplate",
+ )
+ }}:
+
- Receive notifications on:
+ {{
+ $t(
+ "settings.editCoreSettings.general.receiveNotificationsOn",
+ )
+ }}:
- Agent Debug Level:
+
+ {{
+ $t("settings.editCoreSettings.general.agentDebugLevel")
+ }}:
+
- Clear faults on agents that haven't checked in after (days):
+ {{
+ $t(
+ "settings.editCoreSettings.general.clearFaultsAfterDays",
+ )
+ }}:
- Reset Patch Policy on Agents:
+
+ {{
+ $t(
+ "settings.editCoreSettings.general.resetPatchPolicyOnAgents",
+ )
+ }}:
+
@@ -233,7 +351,11 @@
- Email Alert Routing
+
+ {{
+ $t("settings.editCoreSettings.email.emailAlertRouting")
+ }}
+
- Recipients
+
+ {{ $t("settings.editCoreSettings.email.recipients") }}
+
- No recipients
+ {{
+ $t("settings.editCoreSettings.email.noRecipients")
+ }}
- SMTP Settings
+
+ {{ $t("settings.editCoreSettings.email.smtpSettings") }}
+
- From email:
+
+ {{ $t("settings.editCoreSettings.email.fromEmail") }}:
+
- From name:
+
+ {{ $t("settings.editCoreSettings.email.fromName") }}:
+
- Host:
+
+ {{ $t("settings.editCoreSettings.email.host") }}:
+
- Port:
+
+ {{ $t("settings.editCoreSettings.email.port") }}:
+
@@ -338,7 +482,7 @@
class="row"
v-show="settings.smtp_requires_auth"
>
- Username:
+ {{ $t("settings.common.username") }}:
- Password:
+ {{ $t("settings.common.password") }}:
- SMS Alert Routing
+
+ {{ $t("settings.editCoreSettings.sms.smsAlertRouting") }}
+
- Recipients
+
+ {{ $t("settings.editCoreSettings.sms.recipients") }}
+
- No recipients
+ {{
+ $t("settings.editCoreSettings.sms.noRecipients")
+ }}
- Twilio Settings
+
+ {{ $t("settings.editCoreSettings.sms.twilioSettings") }}
+
- Twilio Number:
+
+ {{ $t("settings.editCoreSettings.sms.twilioNumber") }}:
+
- Twilio Account SID:
+
+ {{ $t("settings.editCoreSettings.sms.twilioAccountSid") }}:
+
- Twilio Auth Token:
+
+ {{ $t("settings.editCoreSettings.sms.twilioAuthToken") }}:
+
- MeshCentral Settings
+
+ {{ $t("settings.editCoreSettings.mesh.title") }}
+
- Username:
+ {{ $t("settings.common.username") }}:
(val == val.toLowerCase() &&
val != val.toUpperCase()) ||
- 'Username must be all lowercase',
+ $t('settings.editCoreSettings.mesh.usernameLowercase'),
]"
/>
- Mesh Site:
+
+ {{ $t("settings.editCoreSettings.mesh.meshSite") }}:
+
- Mesh Token:
+
+ {{ $t("settings.editCoreSettings.mesh.meshToken") }}:
+
- Mesh Device Group Name:
+
+ {{
+ $t("settings.editCoreSettings.mesh.meshDeviceGroupName")
+ }}:
+
- Sync Mesh Perms with TRMM:
+ {{
+ $t("settings.editCoreSettings.mesh.syncPermsWithTrmm")
+ }}:
- It is recommended to keep this option enabled;
- otherwise, all TRMM users will have full permissions in
- MeshCentral regardless of their permissions in TRMM.
+ {{
+ $t("settings.editCoreSettings.mesh.syncPermsTooltip")
+ }}
@@ -529,17 +699,18 @@
- Company Name:
+ {{ $t("settings.editCoreSettings.mesh.companyName") }}:
- Adding your company name here will append it to the
- user's full name that appears when doing a remote
- control session, for example: 'John Doe - Amidaware
- Inc.'
+ {{
+ $t(
+ "settings.editCoreSettings.mesh.companyNameTooltip",
+ )
+ }}
@@ -579,69 +750,101 @@
- Check History (days):
+
+ {{
+ $t(
+ "settings.editCoreSettings.retention.checkHistoryDays",
+ )
+ }}:
+
- Resolved Alerts (days):
+
+ {{
+ $t(
+ "settings.editCoreSettings.retention.resolvedAlertsDays",
+ )
+ }}:
+
- Agent History (days):
+
+ {{
+ $t(
+ "settings.editCoreSettings.retention.agentHistoryDays",
+ )
+ }}:
+
- Debug Logs (days):
+
+ {{
+ $t("settings.editCoreSettings.retention.debugLogsDays")
+ }}:
+
- Audit Logs (days):
+
+ {{
+ $t("settings.editCoreSettings.retention.auditLogsDays")
+ }}:
+
- Report History (days):
+
+ {{
+ $t(
+ "settings.editCoreSettings.retention.reportHistoryDays",
+ )
+ }}:
+
@@ -713,13 +916,13 @@
tab === 'meshcentral' ||
tab === 'retention'
"
- label="Save"
+ :label="$t('settings.common.save')"
color="primary"
type="submit"
/>
{
this.settings.sync_mesh_with_trmm = newValue;
@@ -860,14 +1074,14 @@ export default {
toggleAddEmail() {
this.$q
.dialog({
- title: "Add email",
+ title: this.$t("settings.editCoreSettings.email.addEmailTitle"),
prompt: {
model: "",
isValid: (val) => this.isValidEmail(val),
type: "email",
},
cancel: true,
- ok: { label: "Add", color: "primary" },
+ ok: { label: this.$t("settings.common.add"), color: "primary" },
persistent: false,
})
.onOk((data) => {
@@ -877,15 +1091,14 @@ export default {
toggleAddSMSNumber() {
this.$q
.dialog({
- title: "Add number",
- message:
- "Use E.164 format: must have the + symbol and country code, followed by the phone number e.g. +12131231234",
+ title: this.$t("settings.editCoreSettings.sms.addNumberTitle"),
+ message: this.$t("settings.editCoreSettings.sms.addNumberMessage"),
prompt: {
model: "",
},
html: true,
cancel: true,
- ok: { label: "Add", color: "primary" },
+ ok: { label: this.$t("settings.common.add"), color: "primary" },
persistent: false,
})
.onOk((data) => {
@@ -912,7 +1125,11 @@ export default {
.then(() => {
this.$q.loading.hide();
if (this.emailTest) {
- this.$q.loading.show({ message: "Sending test email..." });
+ this.$q.loading.show({
+ message: this.$t(
+ "settings.editCoreSettings.email.sendingTestEmail",
+ ),
+ });
this.$axios
.post("/core/emailtest/")
.then((r) => {
@@ -926,7 +1143,9 @@ export default {
this.$q.loading.hide();
});
} else if (this.smsTest) {
- this.$q.loading.show({ message: "Sending test SMS..." });
+ this.$q.loading.show({
+ message: this.$t("settings.editCoreSettings.sms.sendingTestSms"),
+ });
this.$axios
.post("/core/smstest/")
.then((r) => {
@@ -942,7 +1161,9 @@ export default {
} else {
this.$emit("close");
this.$store.dispatch("getDashInfo", false);
- this.notifySuccess("Settings were edited!");
+ this.notifySuccess(
+ this.$t("settings.editCoreSettings.notify.saved"),
+ );
}
})
.catch(() => {
diff --git a/src/components/modals/coresettings/KeyStoreForm.vue b/src/components/modals/coresettings/KeyStoreForm.vue
index 7c5ac4b4..91782877 100644
--- a/src/components/modals/coresettings/KeyStoreForm.vue
+++ b/src/components/modals/coresettings/KeyStoreForm.vue
@@ -5,30 +5,32 @@
{{ title }}
- Close
+ {{
+ $t("settings.common.close")
+ }}
-
-
+
+
@@ -67,7 +74,9 @@ export default {
},
computed: {
title() {
- return this.editing ? "Edit Global Key" : "Add Global Key";
+ return this.editing
+ ? this.$t("settings.keyStoreForm.editTitle")
+ : this.$t("settings.keyStoreForm.addTitle");
},
editing() {
return !!this.globalKey;
@@ -87,7 +96,7 @@ export default {
.then(() => {
this.$q.loading.hide();
this.onOk();
- this.notifySuccess("Key was edited!");
+ this.notifySuccess(this.$t("settings.keyStoreForm.notify.edited"));
})
.catch(() => {
this.$q.loading.hide();
@@ -98,7 +107,7 @@ export default {
.then(() => {
this.$q.loading.hide();
this.onOk();
- this.notifySuccess("Key was added!");
+ this.notifySuccess(this.$t("settings.keyStoreForm.notify.added"));
})
.catch(() => {
this.$q.loading.hide();
diff --git a/src/components/modals/coresettings/KeyStoreTable.vue b/src/components/modals/coresettings/KeyStoreTable.vue
index c18ac8f2..e7d8607b 100644
--- a/src/components/modals/coresettings/KeyStoreTable.vue
+++ b/src/components/modals/coresettings/KeyStoreTable.vue
@@ -1,14 +1,18 @@
- Global Key Store
+ {{ $t("settings.keyStoreTable.title") }}
@@ -17,7 +21,7 @@
color="grey-5"
icon="fas fa-plus"
text-color="black"
- label="Add key"
+ :label="$t('settings.keyStoreTable.addKey')"
@click="addKey"
/>
@@ -32,7 +36,7 @@
hide-pagination
virtual-scroll
:rows-per-page-options="[0]"
- no-data-label="No Keys added yet"
+ :no-data-label="$t('settings.keyStoreTable.noData')"
>
@@ -48,19 +52,25 @@
- Edit
+ {{
+ $t("settings.common.edit")
+ }}
- Delete
+ {{
+ $t("settings.common.delete")
+ }}
- Close
+ {{
+ $t("settings.common.close")
+ }}
@@ -97,14 +107,14 @@ export default {
columns: [
{
name: "name",
- label: "Name",
+ label: this.$t("settings.common.name"),
field: "name",
align: "left",
sortable: true,
},
{
name: "value",
- label: "Value",
+ label: this.$t("settings.keyStoreTable.columns.value"),
field: "value",
align: "left",
sortable: true,
@@ -150,9 +160,11 @@ export default {
deleteKey(key) {
this.$q
.dialog({
- title: `Delete key: ${key.name}?`,
+ title: this.$t("settings.keyStoreTable.deleteTitle", {
+ name: key.name,
+ }),
cancel: true,
- ok: { label: "Delete", color: "negative" },
+ ok: { label: this.$t("settings.common.delete"), color: "negative" },
})
.onOk(() => {
this.$q.loading.show();
@@ -161,7 +173,11 @@ export default {
.then(() => {
this.getKeyStore();
this.$q.loading.hide();
- this.notifySuccess(`key: ${key.name} was deleted!`);
+ this.notifySuccess(
+ this.$t("settings.keyStoreTable.notify.deleted", {
+ name: key.name,
+ }),
+ );
})
.catch(() => {
this.$q.loading.hide();
diff --git a/src/components/modals/coresettings/ResetPatchPolicy.vue b/src/components/modals/coresettings/ResetPatchPolicy.vue
index 37060261..8d6a82b8 100644
--- a/src/components/modals/coresettings/ResetPatchPolicy.vue
+++ b/src/components/modals/coresettings/ResetPatchPolicy.vue
@@ -2,16 +2,16 @@
- Reset Agent Patch Policy
+ {{ t("settings.resetPatchPolicy.title") }}
- Close
+ {{
+ t("settings.common.close")
+ }}
- Reset the patch policies for agents in a specific client or site. You
- can also leave the client and site blank to reset the patch policy for
- all agents. (This might take a while)
+ {{ t("settings.resetPatchPolicy.description") }}
@@ -27,8 +27,8 @@
-
+
- Testing {{ urlAction.name }}
+ {{ t("settings.testUrlAction.testing", { name: urlAction.name }) }}
- Close
+ {{
+ t("settings.common.close")
+ }}
@@ -22,7 +24,7 @@
- URL:
+ {{ t("settings.testUrlAction.url") }}:
{{ return_url }}
- Body
+ {{ t("settings.testUrlAction.body") }}
{{ return_request }}
- Response
+ {{ t("settings.testUrlAction.response") }}
{{ return_result }}
-
+
@@ -91,6 +93,7 @@
// composition imports
import { ref, reactive, computed } from "vue";
import { useDialogPluginComponent } from "quasar";
+import { useI18n } from "vue-i18n";
import { useAgentDropdown } from "@/composables/agents";
import { useSiteDropdown, useClientDropdown } from "@/composables/clients";
import { runTestURLAction } from "@/api/core";
@@ -107,6 +110,7 @@ const props = defineProps<{ urlAction: URLAction }>();
// setup quasar
const { dialogRef, onDialogHide } = useDialogPluginComponent();
+const { t } = useI18n();
// setup dropdowns
const { agent, agentOptions } = useAgentDropdown({ onMount: true });
@@ -116,10 +120,10 @@ const { site, siteOptions } = useSiteDropdown(true);
const runAgainst = ref<"agent" | "site" | "client" | "none">("none");
const runAgainstOptions = [
- { label: "Agent", value: "agent" },
- { label: "Site", value: "site" },
- { label: "Client", value: "client" },
- { label: "None", value: "none" },
+ { label: t("settings.testUrlAction.runAgainst.agent"), value: "agent" },
+ { label: t("settings.testUrlAction.runAgainst.site"), value: "site" },
+ { label: t("settings.testUrlAction.runAgainst.client"), value: "client" },
+ { label: t("settings.testUrlAction.runAgainst.none"), value: "none" },
];
const loading = ref(false);
diff --git a/src/components/modals/coresettings/URLActionsForm.vue b/src/components/modals/coresettings/URLActionsForm.vue
index 76c771a3..a992a9b2 100644
--- a/src/components/modals/coresettings/URLActionsForm.vue
+++ b/src/components/modals/coresettings/URLActionsForm.vue
@@ -13,15 +13,17 @@
{{
props.action
? props.type === "web"
- ? "Edit URL Action"
- : "Edit Web Hook"
+ ? t("settings.urlActionsForm.editUrlAction")
+ : t("settings.urlActionsForm.editWebhook")
: props.type === "web"
- ? "Add URL Action"
- : "Add Web Hook"
+ ? t("settings.urlActionsForm.addUrlAction")
+ : t("settings.urlActionsForm.addWebhook")
}}
- Close
+ {{
+ t("settings.common.close")
+ }}
@@ -29,18 +31,18 @@
-
+
@@ -92,12 +98,17 @@
-
-
+
+
@@ -107,6 +118,7 @@
// composition imports
import { ref, computed, reactive, watch } from "vue";
import { useDialogPluginComponent, useQuasar, extend } from "quasar";
+import { useI18n } from "vue-i18n";
import { editURLAction, saveURLAction } from "@/api/core";
import { notifySuccess } from "@/utils/notify";
import { URLAction, URLActionType } from "@/types/core/urlactions";
@@ -124,6 +136,7 @@ const props = defineProps<{ type: URLActionType; action?: URLAction }>();
// setup quasar
const $q = useQuasar();
+const { t } = useI18n();
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// static data
@@ -167,7 +180,7 @@ async function submit() {
? await editURLAction(localAction.id, localAction)
: await saveURLAction(localAction);
onDialogOK();
- notifySuccess("Url Action was edited!");
+ notifySuccess(t("settings.urlActionsForm.notify.saved"));
} catch (e) {}
$q.loading.hide();
diff --git a/src/components/modals/coresettings/URLActionsTable.vue b/src/components/modals/coresettings/URLActionsTable.vue
index f8cfc85b..ec7b9a46 100644
--- a/src/components/modals/coresettings/URLActionsTable.vue
+++ b/src/components/modals/coresettings/URLActionsTable.vue
@@ -4,8 +4,8 @@
{{
props.type === "web"
- ? "URL Actions"
- : "Web Hooks for Alert Failure/Resolved Actions"
+ ? t("settings.urlActionsTable.urlActions")
+ : t("settings.urlActionsTable.webhooks")
}}
@@ -14,7 +14,11 @@
color="grey-5"
icon="fas fa-plus"
text-color="black"
- :label="`Add ${props.type === 'web' ? 'URL Action' : 'Web Hook'}`"
+ :label="
+ props.type === 'web'
+ ? t('settings.urlActionsTable.addUrlAction')
+ : t('settings.urlActionsTable.addWebhook')
+ "
@click="addURLAction"
/>
@@ -29,7 +33,11 @@
hide-pagination
virtual-scroll
:rows-per-page-options="[0]"
- :no-data-label="`No ${props.type === 'web' ? 'URL Actions' : 'Web Hooks'} added yet`"
+ :no-data-label="
+ props.type === 'web'
+ ? t('settings.urlActionsTable.noUrlActions')
+ : t('settings.urlActionsTable.noWebhooks')
+ "
:loading="loading"
>
@@ -46,7 +54,7 @@
- Edit
+ {{ t("settings.common.edit") }}
- Delete
+ {{
+ t("settings.common.delete")
+ }}
- Close
+ {{
+ t("settings.common.close")
+ }}
@@ -88,6 +100,7 @@
// composition imports
import { ref, onMounted } from "vue";
import { QTableColumn, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
import { fetchURLActions, removeURLAction } from "@/api/core";
import { notifySuccess } from "@/utils/notify";
import { truncateText } from "@/utils/format";
@@ -103,6 +116,7 @@ const props = defineProps<{ type: URLActionType }>();
// setup quasar
const $q = useQuasar();
+const { t } = useI18n();
const loading = ref(false);
@@ -111,21 +125,21 @@ const actions = ref([] as URLAction[]);
const columns: QTableColumn[] = [
{
name: "name",
- label: "Name",
+ label: t("settings.common.name"),
field: "name",
align: "left",
sortable: true,
},
{
name: "desc",
- label: "Description",
+ label: t("settings.urlActionsTable.columns.description"),
field: "desc",
align: "left",
sortable: true,
},
{
name: "pattern",
- label: "URL Pattern",
+ label: t("settings.urlActionsTable.columns.urlPattern"),
field: "pattern",
align: "left",
sortable: true,
@@ -167,15 +181,17 @@ function editURLAction(action: URLAction) {
function deleteURLAction(action: URLAction) {
$q.dialog({
- title: `Delete URL Action: ${action.name}?`,
+ title: t("settings.urlActionsTable.deleteTitle", { name: action.name }),
cancel: true,
- ok: { label: "Delete", color: "negative" },
+ ok: { label: t("settings.common.delete"), color: "negative" },
}).onOk(async () => {
loading.value = true;
try {
await removeURLAction(action.id);
await getURLActions();
- notifySuccess(`URL Action: ${action.name} was deleted!`);
+ notifySuccess(
+ t("settings.urlActionsTable.notify.deleted", { name: action.name }),
+ );
} catch (e) {
console.error(e);
}
diff --git a/src/components/modals/coresettings/UserPreferences.vue b/src/components/modals/coresettings/UserPreferences.vue
index 81e7d918..5b10843c 100644
--- a/src/components/modals/coresettings/UserPreferences.vue
+++ b/src/components/modals/coresettings/UserPreferences.vue
@@ -4,13 +4,18 @@
-
+
- Preferences
+
+ {{ $t("settings.userPreferences.title") }}
+
@@ -22,10 +27,14 @@
>
- User Interface
+
+ {{ $t("settings.userPreferences.userInterface") }}
+
- Agent double-click action:
+
+ {{ $t("settings.userPreferences.agentDoubleClickAction") }}:
+
- URL Action:
+
+ {{ $t("settings.userPreferences.urlAction") }}:
+
- Agent table default tab:
+
+ {{ $t("settings.userPreferences.agentTableDefaultTab") }}:
+
- Loading Bar Color:
+
+ {{ $t("settings.userPreferences.loadingBarColor") }}:
+
- Dashboard Info Color:
+
+ {{ $t("settings.userPreferences.dashboardInfoColor") }}:
+
- Click to see color options
+ {{
+ $t("settings.userPreferences.clickForColorOptions")
+ }}
- Dashboard Positive Color:
+
+ {{ $t("settings.userPreferences.dashboardPositiveColor") }}:
+
- Click to see color options
+ {{
+ $t("settings.userPreferences.clickForColorOptions")
+ }}
- Dashboard Negative Color:
+
+ {{ $t("settings.userPreferences.dashboardNegativeColor") }}:
+
- Click to see color options
+ {{
+ $t("settings.userPreferences.clickForColorOptions")
+ }}
- Dashboard Warning Color:
+
+ {{ $t("settings.userPreferences.dashboardWarningColor") }}:
+
- Click to see color options
+ {{
+ $t("settings.userPreferences.clickForColorOptions")
+ }}
- Client Sort:
+
+ {{ $t("settings.userPreferences.language") }}:
+
+
+
+
+
+
+ {{ $t("settings.userPreferences.clientSort") }}:
+
- Date Format:
+
+ {{ $t("settings.userPreferences.dateFormat") }}:
+
@@ -205,7 +258,11 @@
)
"
>
- Click to see formatting options
+ {{
+ $t(
+ "settings.userPreferences.clickForFormattingOptions",
+ )
+ }}
@@ -213,14 +270,20 @@
-
+
@@ -231,6 +294,13 @@
diff --git a/src/components/ui/WinUpdateDialog.vue b/src/components/ui/WinUpdateDialog.vue
index b830d4d9..ebd966c6 100644
--- a/src/components/ui/WinUpdateDialog.vue
+++ b/src/components/ui/WinUpdateDialog.vue
@@ -6,11 +6,11 @@
- Categories:
+ {{ $t("settings.winUpdateDialog.categories") }}:
{{ categories.join(", ") }}
- Description
+ {{ $t("settings.winUpdateDialog.description") }}
- Support URLs
+ {{ $t("settings.winUpdateDialog.supportUrls") }}
-
+
diff --git a/src/composables/checks.js b/src/composables/checks.js
index e2776c16..88e64a1c 100644
--- a/src/composables/checks.js
+++ b/src/composables/checks.js
@@ -25,7 +25,7 @@ export function useCheckModal({ editCheck, initialState }) {
if (
!isValidThreshold(
check.value.warning_threshold,
- check.value.error_threshold
+ check.value.error_threshold,
)
)
return;
@@ -34,7 +34,7 @@ export function useCheckModal({ editCheck, initialState }) {
!isValidThreshold(
check.value.warning_threshold,
check.value.error_threshold,
- true
+ true,
)
)
return;
@@ -70,8 +70,8 @@ export function useCheckModal({ editCheck, initialState }) {
const diskOptions = ref(
"A:,B:,C:,D:,E:,F:,G:,H:,I:,J:,K:,L:,M:,N:,O:,P:,Q:,R:,S:,T:,U:,V:,W:,X:,Y:,Z:".split(
- ","
- )
+ ",",
+ ),
);
const serviceOptions = ref(Object.freeze(defaultServiceOptions));
@@ -90,7 +90,7 @@ export function useCheckModal({ editCheck, initialState }) {
value: service.name,
}));
serviceOptions.value = Object.freeze(
- tmp.sort((a, b) => a.label.localeCompare(b.label))
+ tmp.sort((a, b) => a.label.localeCompare(b.label)),
);
check.value.svc_name = serviceOptions.value[0].value;
check.value.svc_display_name = serviceOptions.value[0].label;
@@ -136,13 +136,13 @@ export function useCheckDropdown() {
async function getCheckOptions({ agent, policy }, flat = false) {
if (!agent && !policy) {
console.error(
- "Need to specify agent or policy object when calling getCheckOptions"
+ "Need to specify agent or policy object when calling getCheckOptions",
);
return;
}
checkOptions.value = formatCheckOptions(
agent ? await fetchAgentChecks(agent) : await fetchPolicyChecks(policy),
- flat
+ flat,
);
}
diff --git a/src/composables/filebrowser.ts b/src/composables/filebrowser.ts
index 1a7e3a52..14dd38b2 100644
--- a/src/composables/filebrowser.ts
+++ b/src/composables/filebrowser.ts
@@ -7,7 +7,7 @@ export function useFileBrowser() {
name: string,
path: string,
size = "0",
- asset_id?: string
+ asset_id?: string,
): QTreeFileNode {
return {
id: uid(),
@@ -24,7 +24,7 @@ export function useFileBrowser() {
name: string,
path: string,
icon = "folder",
- color = "yellow-9"
+ color = "yellow-9",
): QTreeFileNode {
return {
id: uid(),
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index 99f8c7cd..aa54eb30 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -1,50 +1,4 @@
-import type { QTableColumn } from "quasar";
-
export const GOARCH_AMD64 = "amd64";
export const GOARCH_i386 = "386";
export const GOARCH_ARM64 = "arm64";
export const GOARCH_ARM32 = "arm";
-
-export const runAsUserToolTip =
- "Run in the context of the logged in user. If no user is logged in, the script will run as SYSTEM";
-
-export const envVarsLabel =
- "Environment vars (press Enter after typing each key=value pair)";
-
-export const registryTableColumns: QTableColumn[] = [
- {
- name: "name",
- label: "Name",
- field: "name",
- align: "left",
- style:
- "width: 240px; max-width: 240px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;",
- headerStyle: "width: 240px; max-width: 240px;",
- },
- {
- name: "type",
- label: "Type",
- field: "type",
- align: "left",
- style: "width: 160px; max-width: 160px; white-space: nowrap;",
- headerStyle: "width: 160px; max-width: 160px;",
- },
- {
- name: "data",
- label: "Data",
- field: "data",
- align: "left",
- style:
- "min-width: 300px; max-width: 300px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;",
- headerStyle: "min-width: 300px; max-width: 300px;",
- },
-];
-
-export const registryValueTypes = [
- { label: "Key", type: "KEY" },
- { label: "String Value", type: "REG_SZ" },
- { label: "DWORD (32-bit) Value", type: "REG_DWORD" },
- { label: "QWORD (64-bit) Value", type: "REG_QWORD" },
- { label: "Multi-String Value", type: "REG_MULTI_SZ" },
- { label: "Expandable String Value", type: "REG_EXPAND_SZ" },
-];
diff --git a/src/core/dashboard/ui/TacticalTable.vue b/src/core/dashboard/ui/TacticalTable.vue
index 1be820de..8648b1a4 100644
--- a/src/core/dashboard/ui/TacticalTable.vue
+++ b/src/core/dashboard/ui/TacticalTable.vue
@@ -43,7 +43,9 @@ export default defineComponent({
diff --git a/src/ee/reporting/components/ReportTemplatePreview.vue b/src/ee/reporting/components/ReportTemplatePreview.vue
index 1016e75c..476a22bd 100644
--- a/src/ee/reporting/components/ReportTemplatePreview.vue
+++ b/src/ee/reporting/components/ReportTemplatePreview.vue
@@ -31,7 +31,11 @@ For details, see: https://license.tacticalrmm.com/ee
- {{ previewFormat === "plaintext" ? "Text" : "HTML" }}
+ {{
+ previewFormat === "plaintext"
+ ? t("reporting.common.text")
+ : t("reporting.common.html")
+ }}
- Variables
+ {{ t("reporting.common.variables") }}
();
const $q = useQuasar();
+const { t } = useI18n();
const splitterHeight = ref($q.screen.height - 82);
diff --git a/src/ee/reporting/components/ReportingHelpMenu.vue b/src/ee/reporting/components/ReportingHelpMenu.vue
index af998c55..419182d1 100644
--- a/src/ee/reporting/components/ReportingHelpMenu.vue
+++ b/src/ee/reporting/components/ReportingHelpMenu.vue
@@ -6,50 +6,47 @@ For details, see: https://license.tacticalrmm.com/ee
- Report Template
+ {{ t("reporting.helpMenu.reportTemplate") }}
- Report Templates
+
+ {{ t("reporting.helpMenu.reportTemplates") }}
+
- Base Template
+ {{ t("reporting.helpMenu.baseTemplate") }}
- Test
+ {{ t("reporting.helpMenu.test") }}
- Data Query
+ {{ t("reporting.helpMenu.dataQuery") }}
- Data Queries are used to save common database queries to use them in
- templates. Behind the scenes, we are just creating a Django queryset.
- The only difference is these querysets are restricted to only retrieve
- data versus modifying data.
+ {{ t("reporting.helpMenu.dataQueryDescription") }}
- Syntax
+ {{ t("reporting.helpMenu.syntax") }}
- When you create Data Queries in the Data Query Editor you use JSON.
- You can also create Data Queries directly in the template variables
- which uses yaml syntax.
+ {{ t("reporting.helpMenu.syntaxDescription") }}
- Structure
+ {{ t("reporting.helpMenu.structure") }}
- Ctrl+Space in the query editor to auto-complete values
+ {{ t("reporting.helpMenu.structureDescription") }}
- - * model (*string)
+ - {{ t("reporting.helpMenu.modelField") }}
-
- This is the only required field. This specifies the table to query.
+ {{ t("reporting.helpMenu.modelFieldDescription") }}
- - * filter (object)
+ - {{ t("reporting.helpMenu.filterField") }}
@@ -58,7 +55,11 @@ For details, see: https://license.tacticalrmm.com/ee
diff --git a/src/ee/reporting/components/ReportsManager.vue b/src/ee/reporting/components/ReportsManager.vue
index a71e15ca..87dc871e 100644
--- a/src/ee/reporting/components/ReportsManager.vue
+++ b/src/ee/reporting/components/ReportsManager.vue
@@ -15,10 +15,12 @@ For details, see: https://license.tacticalrmm.com/ee
push
icon="refresh"
@click="getReportTemplates()"
- />Reports Manager
+ />{{ t("reporting.reportsManager.title") }}
- Close
+ {{
+ t("reporting.common.close")
+ }}
- Markdown Template
+ {{
+ t("reporting.reportsManager.markdownTemplate")
+ }}
@@ -65,7 +69,9 @@ For details, see: https://license.tacticalrmm.com/ee
@click="openNewReportTemplateForm('html')"
>
- HTML Template
+ {{
+ t("reporting.reportsManager.htmlTemplate")
+ }}
@@ -75,7 +81,9 @@ For details, see: https://license.tacticalrmm.com/ee
@click="openNewReportTemplateForm('plaintext')"
>
- Plain Text Template
+ {{
+ t("reporting.reportsManager.plainTextTemplate")
+ }}
@@ -83,14 +91,16 @@ For details, see: https://license.tacticalrmm.com/ee
- Import Report Template
+ {{
+ t("reporting.reportsManager.importReportTemplate")
+ }}
- Edit
+ {{
+ t("reporting.common.edit")
+ }}
- Clone
+ {{
+ t("reporting.common.clone")
+ }}
@@ -186,7 +200,9 @@ For details, see: https://license.tacticalrmm.com/ee
openReport(props.row.id, 'pdf', props.row.depends_on, {})
"
>
- Open PDF Report
+ {{
+ t("reporting.reportsManager.openPdfReport")
+ }}
- Open
- {{ props.row.type !== "plaintext" ? "HTML" : "Text" }}
- Report
+ {{
+ t("reporting.reportsManager.openFormatReport", {
+ format:
+ props.row.type !== "plaintext"
+ ? t("reporting.common.html")
+ : t("reporting.common.text"),
+ })
+ }}
@@ -215,7 +234,9 @@ For details, see: https://license.tacticalrmm.com/ee
clickable
@click="downloadReport(props.row, 'pdf', {})"
>
- Download PDF Report
+ {{
+ t("reporting.reportsManager.downloadPdfReport")
+ }}
- Download
- {{ props.row.type !== "plaintext" ? "HTML" : "Text" }}
- Report
+ {{
+ t("reporting.reportsManager.downloadFormatReport", {
+ format:
+ props.row.type !== "plaintext"
+ ? t("reporting.common.html")
+ : t("reporting.common.text"),
+ })
+ }}
@@ -243,11 +267,15 @@ For details, see: https://license.tacticalrmm.com/ee
clickable
@click="scheduleReport(props.row)"
>
- Schedule Report
+ {{
+ t("reporting.reportsManager.scheduleReport")
+ }}
- Email Report
+ {{
+ t("reporting.reportsManager.emailReport")
+ }}
@@ -257,7 +285,9 @@ For details, see: https://license.tacticalrmm.com/ee
clickable
@click="exportReport(props.row.id)"
>
- Export
+ {{
+ t("reporting.common.export")
+ }}
@@ -267,13 +297,17 @@ For details, see: https://license.tacticalrmm.com/ee
clickable
@click="deleteTemplate(props.row)"
>
- Delete
+ {{
+ t("reporting.common.delete")
+ }}
- Close
+ {{
+ t("reporting.common.close")
+ }}
@@ -297,10 +331,11 @@ For details, see: https://license.tacticalrmm.com/ee