From 024bb89baeffcea568372f7007622a3b3be74175 Mon Sep 17 00:00:00 2001 From: FredGuiou Date: Wed, 25 Jun 2025 21:54:53 +0200 Subject: [PATCH] feat(interface): add lang selector in settings page --- i18n/english.js | 9 ++++++--- i18n/french.js | 11 +++++++---- public/components/views/settings/settings.js | 15 ++++++++++++--- public/main.js | 6 ++++++ src/commands/lang.js | 15 +++++++++++++++ views/index.html | 5 +++++ workspaces/server/src/config.js | 17 ++++++++++++++--- workspaces/server/test/config.test.js | 7 ++++++- workspaces/server/test/httpServer.test.js | 6 ++++-- 9 files changed, 75 insertions(+), 16 deletions(-) diff --git a/i18n/english.js b/i18n/english.js index d08251c0..9d1aa34f 100644 --- a/i18n/english.js +++ b/i18n/english.js @@ -103,7 +103,9 @@ const ui = { vulnerabilities: "vulnerabilities (CVE)", licenses: "licenses conformance (SPDX)", dark: "dark", - light: "light" + light: "light", + fr: "french", + en: "english" }, title: { maintainers: "maintainers", @@ -182,8 +184,9 @@ const ui = { general: { title: "General", save: "save", - defaultPannel: "Default Package Menu", - themePannel: "Interface theme", + defaultPanel: "Default Package Menu", + themePanel: "Interface theme", + langPanel: "Interface language", warnings: "SAST Warnings to ignore", flags: "Flags (emojis) to ignore", network: "Network", diff --git a/i18n/french.js b/i18n/french.js index 8a070902..3de10617 100644 --- a/i18n/french.js +++ b/i18n/french.js @@ -81,7 +81,7 @@ const cli = { startHttp: { invalidScannerVersion: tS`le fichier d'analyse correspond à la version '${0}' du scanner et ne satisfait pas la range '${1}' attendu par la CLI`, regenerate: "veuillez re-générer un nouveau fichier d'analyse JSON en utilisant votre CLI" - }, + } }; const ui = { @@ -103,7 +103,9 @@ const ui = { vulnerabilities: "vulnérabilités", licenses: "conformité des licences (SPDX)", dark: "sombre", - light: "clair" + light: "clair", + fr: "français", + en: "anglais" }, title: { maintainers: "mainteneurs", @@ -182,8 +184,9 @@ const ui = { general: { title: "Général", save: "sauvegarder", - defaultPannel: "Panneau par défaut", - themePannel: "Thème de l'interface", + defaultPanel: "Panneau par défaut", + themePanel: "Thème de l'interface", + langPanel: "Langue de l'interface", warnings: "Avertissements à ignorer", flags: "Drapeau (emojis) à ignorer", network: "Réseau", diff --git a/public/components/views/settings/settings.js b/public/components/views/settings/settings.js index f0139a0d..29d11be2 100644 --- a/public/components/views/settings/settings.js +++ b/public/components/views/settings/settings.js @@ -39,6 +39,7 @@ export class Settings { /** @type {HTMLInputElement} */ showFriendlyDependenciesCheckbox: document.querySelector("#show-friendly"), themeSelector: document.querySelector("#theme_selector"), + langSelector: document.querySelector("#lang_selector"), disableExternalRequestsCheckbox: document.querySelector("#disable-external") }; @@ -52,6 +53,7 @@ export class Settings { ...this.dom.flagsCheckbox, this.dom.showFriendlyDependenciesCheckbox, this.dom.themeSelector, + this.dom.langSelector, this.dom.disableExternalRequestsCheckbox ]; for (const formField of formFields) { @@ -203,7 +205,8 @@ export class Settings { ignore: { flags: new Set(), warnings: new Set() }, showFriendlyDependencies: this.dom.showFriendlyDependenciesCheckbox.checked, theme: this.dom.themeSelector.value, - disableExternalRequests: this.dom.disableExternalRequestsCheckbox.checked + disableExternalRequests: this.dom.disableExternalRequestsCheckbox.checked, + lang: this.dom.langSelector.value }; for (const checkbox of this.dom.warningsCheckbox) { @@ -228,15 +231,21 @@ export class Settings { "content-type": "application/json" } }); - this.config = newConfig; + this.config = { ...newConfig, lang: this.config.lang }; this.saveButton.classList.add("disabled"); - window.dispatchEvent(new CustomEvent("settings-saved", { detail: this.config })); + window.dispatchEvent(new CustomEvent("settings-saved", { + detail: { + ...this.config, + lang: newConfig.lang + } + })); } updateSettings() { this.dom.defaultPackageMenu.value = this.config.defaultPackageMenu; this.dom.themeSelector.value = this.config.theme; + this.dom.langSelector.value = this.config.lang; const warnings = new Set(this.config.ignore.warnings); const flags = new Set(this.config.ignore.flags); diff --git a/public/main.js b/public/main.js index 8e0ebfc6..b5e3d548 100644 --- a/public/main.js +++ b/public/main.js @@ -209,15 +209,21 @@ async function updateShowInfoMenu(params) { function onSettingsSaved(defaultConfig = null) { async function updateSettings(config) { console.log("[INFO] Settings saved:", config); + if (window.settings.config.lang !== config.lang) { + window.location.reload(); + } + const warningsToIgnore = new Set(config.ignore.warnings); const flagsToIgnore = new Set(config.ignore.flags); const theme = config.theme; + const lang = config.lang; secureDataSet.warningsToIgnore = warningsToIgnore; secureDataSet.flagsToIgnore = flagsToIgnore; secureDataSet.theme = theme; window.settings.config.ignore.warnings = warningsToIgnore; window.settings.config.ignore.flags = flagsToIgnore; window.settings.config.theme = theme; + window.settings.config.lang = lang; window.settings.config.disableExternalRequests = config.disableExternalRequests; if (theme === "dark") { diff --git a/src/commands/lang.js b/src/commands/lang.js index 25f2af76..df6573fa 100644 --- a/src/commands/lang.js +++ b/src/commands/lang.js @@ -1,5 +1,6 @@ // Import Third-party Dependencies import * as i18n from "@nodesecure/i18n"; +import { appCache } from "@nodesecure/cache"; import { select } from "@topcli/prompts"; import kleur from "kleur"; @@ -15,6 +16,20 @@ export async function set() { await i18n.setLocalLang(selectedLang); await i18n.getLocalLang(); + try { + const config = await appCache.getConfig(); + + if (config) { + await appCache.updateConfig({ + ...config, + lang: selectedLang + }); + } + } + catch { + // Config does not exist, do nothing + } + console.log( kleur.white().bold(`\n ${i18n.getTokenSync("cli.commands.lang.new_selection", kleur.yellow().bold(selectedLang))}`) ); diff --git a/views/index.html b/views/index.html index caead651..1bc3ebc6 100644 --- a/views/index.html +++ b/views/index.html @@ -121,6 +121,11 @@

[[=z.token('settings.general.title')]]

+ +

[[=z.token('settings.general.network')]]:

diff --git a/workspaces/server/src/config.js b/workspaces/server/src/config.js index a96fe8b2..69f46460 100644 --- a/workspaces/server/src/config.js +++ b/workspaces/server/src/config.js @@ -1,6 +1,7 @@ // Import Third-party Dependencies import { warnings } from "@nodesecure/js-x-ray"; import { appCache } from "@nodesecure/cache"; +import * as i18n from "@nodesecure/i18n"; // Import Internal Dependencies import { logger } from "./logger.js"; @@ -16,6 +17,7 @@ const kDefaultConfig = { }; export async function get() { + const localLang = await i18n.getLocalLang(); try { const config = await appCache.getConfig(); @@ -26,7 +28,8 @@ export async function get() { warnings } = {}, theme, - disableExternalRequests = false + disableExternalRequests = false, + lang = localLang } = config; logger.info( // eslint-disable-next-line @stylistic/max-len @@ -40,7 +43,8 @@ export async function get() { warnings }, theme, - disableExternalRequests + disableExternalRequests, + lang }; } catch (err) { @@ -50,7 +54,7 @@ export async function get() { logger.info(`[config|get](fallback to default: ${JSON.stringify(kDefaultConfig)})`); - return kDefaultConfig; + return { ...kDefaultConfig, lang: localLang }; } } @@ -66,4 +70,11 @@ export async function set(newValue) { throw err; } + + const i18nLocalLang = await i18n.getLocalLang(); + if (i18nLocalLang !== newValue.lang) { + logger.info(`[config|set](updating i18n lang to: ${newValue.lang})`); + await i18n.setLocalLang(newValue.lang); + await i18n.getLanguages(); + } } diff --git a/workspaces/server/test/config.test.js b/workspaces/server/test/config.test.js index 03d4d2a8..d8c5c330 100644 --- a/workspaces/server/test/config.test.js +++ b/workspaces/server/test/config.test.js @@ -6,6 +6,7 @@ import assert from "node:assert"; import cacache from "cacache"; import { warnings } from "@nodesecure/js-x-ray"; import { CACHE_PATH } from "@nodesecure/cache"; +import * as i18n from "@nodesecure/i18n"; // Import Internal Dependencies import { get, set } from "../src/config.js"; @@ -17,6 +18,7 @@ describe("config", () => { let actualConfig; before(async() => { + await i18n.getLanguages(); actualConfig = await get(); }); @@ -33,7 +35,8 @@ describe("config", () => { ignore: { flags: [], warnings: Object.entries(warnings) .filter(([_, { experimental }]) => experimental) .map(([warning]) => warning) }, - disableExternalRequests: false + disableExternalRequests: false, + lang: await i18n.getLocalLang() }); }); @@ -44,6 +47,7 @@ describe("config", () => { flags: ["foo"], warnings: ["bar"] }, + lang: "english", theme: "galaxy", disableExternalRequests: true }; @@ -60,6 +64,7 @@ describe("config", () => { flags: ["foz"], warnings: ["baz"] }, + lang: "english", theme: "galactic", disableExternalRequests: true }; diff --git a/workspaces/server/test/httpServer.test.js b/workspaces/server/test/httpServer.test.js index 65f7690b..8365a136 100644 --- a/workspaces/server/test/httpServer.test.js +++ b/workspaces/server/test/httpServer.test.js @@ -223,6 +223,7 @@ describe("httpServer", { concurrency: 1 }, () => { flags: ["foo"], warnings: ["bar"] }, + lang: "english", theme: "galaxy", disableExternalRequests: true }; @@ -240,19 +241,20 @@ describe("httpServer", { concurrency: 1 }, () => { }); test("PUT '/config' should update the config", async() => { + const lang = await i18n.getLocalLang(); const { data: actualConfig } = await get(new URL("/config", kHttpURL)); // FIXME: use @mynusift/httpie instead of fetch. Atm it throws with put(). // https://github.com/nodejs/undici/issues/583 const { status } = await fetch(new URL("/config", kHttpURL), { method: "PUT", - body: JSON.stringify({ fooz: "baz" }), + body: JSON.stringify({ fooz: "baz", lang }), headers: { "Content-Type": "application/json" } }); assert.equal(status, 204); const inCache = await cacache.get(CACHE_PATH, kConfigKey); - assert.deepEqual(JSON.parse(inCache.data.toString()), { fooz: "baz" }); + assert.deepEqual(JSON.parse(inCache.data.toString()), { fooz: "baz", lang }); await fetch(new URL("/config", kHttpURL), { method: "PUT",