Skip to content

Commit 59716a6

Browse files
committed
feat(interface): add lang selector in settings page
1 parent 3db4e95 commit 59716a6

File tree

10 files changed

+99
-22
lines changed

10 files changed

+99
-22
lines changed

i18n/english.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ const cli = {
8888
}
8989
};
9090

91+
const languages = {
92+
fr: "french",
93+
en: "english"
94+
};
95+
9196
const ui = {
9297
stats: {
9398
title: "Global Stats",
@@ -105,9 +110,7 @@ const ui = {
105110
dependencies: "scripts & dependencies",
106111
warnings: "threats in source code",
107112
vulnerabilities: "vulnerabilities (CVE)",
108-
licenses: "licenses conformance (SPDX)",
109-
dark: "dark",
110-
light: "light"
113+
licenses: "licenses conformance (SPDX)"
111114
},
112115
title: {
113116
maintainers: "maintainers",
@@ -188,8 +191,12 @@ const ui = {
188191
general: {
189192
title: "General",
190193
save: "save",
191-
defaultPannel: "Default Package Menu",
192-
themePannel: "Interface theme",
194+
dark: "dark",
195+
light: "light",
196+
languages,
197+
defaultPanel: "Default Package Menu",
198+
themePanel: "Interface theme",
199+
langPanel: "Interface language",
193200
warnings: "SAST Warnings to ignore",
194201
flags: "Flags (emojis) to ignore",
195202
network: "Network",

i18n/french.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ const cli = {
8888
}
8989
};
9090

91+
const languages = {
92+
fr: "français",
93+
en: "anglais"
94+
};
95+
9196
const ui = {
9297
stats: {
9398
title: "Stats Globales",
@@ -105,9 +110,7 @@ const ui = {
105110
dependencies: "scripts & dépendances",
106111
warnings: "menaces dans le code",
107112
vulnerabilities: "vulnérabilités",
108-
licenses: "conformité des licences (SPDX)",
109-
dark: "sombre",
110-
light: "clair"
113+
licenses: "conformité des licences (SPDX)"
111114
},
112115
title: {
113116
maintainers: "mainteneurs",
@@ -188,8 +191,12 @@ const ui = {
188191
general: {
189192
title: "Général",
190193
save: "sauvegarder",
191-
defaultPannel: "Panneau par défaut",
192-
themePannel: "Thème de l'interface",
194+
dark: "sombre",
195+
light: "clair",
196+
languages,
197+
defaultPanel: "Panneau par défaut",
198+
themePanel: "Thème de l'interface",
199+
langPanel: "Langue de l'interface",
193200
warnings: "Avertissements à ignorer",
194201
flags: "Drapeau (emojis) à ignorer",
195202
network: "Réseau",

public/components/views/settings/settings.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class Settings {
3939
/** @type {HTMLInputElement} */
4040
showFriendlyDependenciesCheckbox: document.querySelector("#show-friendly"),
4141
themeSelector: document.querySelector("#theme_selector"),
42+
langSelector: document.querySelector("#lang_selector"),
4243
disableExternalRequestsCheckbox: document.querySelector("#disable-external")
4344
};
4445

@@ -52,6 +53,7 @@ export class Settings {
5253
...this.dom.flagsCheckbox,
5354
this.dom.showFriendlyDependenciesCheckbox,
5455
this.dom.themeSelector,
56+
this.dom.langSelector,
5557
this.dom.disableExternalRequestsCheckbox
5658
];
5759
for (const formField of formFields) {
@@ -203,7 +205,8 @@ export class Settings {
203205
ignore: { flags: new Set(), warnings: new Set() },
204206
showFriendlyDependencies: this.dom.showFriendlyDependenciesCheckbox.checked,
205207
theme: this.dom.themeSelector.value,
206-
disableExternalRequests: this.dom.disableExternalRequestsCheckbox.checked
208+
disableExternalRequests: this.dom.disableExternalRequestsCheckbox.checked,
209+
lang: this.dom.langSelector.value
207210
};
208211

209212
for (const checkbox of this.dom.warningsCheckbox) {
@@ -228,15 +231,21 @@ export class Settings {
228231
"content-type": "application/json"
229232
}
230233
});
231-
this.config = newConfig;
234+
this.config = { ...newConfig, lang: this.config.lang };
232235
this.saveButton.classList.add("disabled");
233236

234-
window.dispatchEvent(new CustomEvent("settings-saved", { detail: this.config }));
237+
window.dispatchEvent(new CustomEvent("settings-saved", {
238+
detail: {
239+
...this.config,
240+
lang: newConfig.lang
241+
}
242+
}));
235243
}
236244

237245
updateSettings() {
238246
this.dom.defaultPackageMenu.value = this.config.defaultPackageMenu;
239247
this.dom.themeSelector.value = this.config.theme;
248+
this.dom.langSelector.value = this.config.lang;
240249

241250
const warnings = new Set(this.config.ignore.warnings);
242251
const flags = new Set(this.config.ignore.flags);

public/main.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ document.addEventListener("DOMContentLoaded", async() => {
3232
window.navigation = new ViewNavigation();
3333
window.wiki = new Wiki();
3434

35+
const languages = window.i18n.package_info.navigation.languages;
36+
const langSelector = document.getElementById("lang_selector");
37+
if (langSelector && languages) {
38+
langSelector.innerHTML = Object.entries(languages)
39+
.map(([key, label]) => `<option value="${key}">${label}</option>`)
40+
.join("");
41+
}
42+
3543
await init();
3644
onSettingsSaved(window.settings.config);
3745

@@ -211,15 +219,21 @@ async function updateShowInfoMenu(params) {
211219
function onSettingsSaved(defaultConfig = null) {
212220
async function updateSettings(config) {
213221
console.log("[INFO] Settings saved:", config);
222+
if (window.settings.config.lang !== config.lang) {
223+
window.location.reload();
224+
}
225+
214226
const warningsToIgnore = new Set(config.ignore.warnings);
215227
const flagsToIgnore = new Set(config.ignore.flags);
216228
const theme = config.theme;
229+
const lang = config.lang;
217230
secureDataSet.warningsToIgnore = warningsToIgnore;
218231
secureDataSet.flagsToIgnore = flagsToIgnore;
219232
secureDataSet.theme = theme;
220233
window.settings.config.ignore.warnings = warningsToIgnore;
221234
window.settings.config.ignore.flags = flagsToIgnore;
222235
window.settings.config.theme = theme;
236+
window.settings.config.lang = lang;
223237
window.settings.config.disableExternalRequests = config.disableExternalRequests;
224238

225239
if (theme === "dark") {

src/commands/lang.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Import Third-party Dependencies
22
import * as i18n from "@nodesecure/i18n";
3+
import { appCache } from "@nodesecure/cache";
34
import { select } from "@topcli/prompts";
45
import kleur from "kleur";
56

@@ -15,6 +16,20 @@ export async function set() {
1516
await i18n.setLocalLang(selectedLang);
1617
await i18n.getLocalLang();
1718

19+
try {
20+
const config = await appCache.getConfig();
21+
22+
if (config) {
23+
await appCache.updateConfig({
24+
...config,
25+
lang: selectedLang
26+
});
27+
}
28+
}
29+
catch {
30+
// Config does not exist, do nothing
31+
}
32+
1833
console.log(
1934
kleur.white().bold(`\n ${i18n.getTokenSync("cli.commands.lang.new_selection", kleur.yellow().bold(selectedLang))}`)
2035
);

views/index.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,13 @@ <h1><i class="icon-cog"></i>[[=z.token('settings.general.title')]]</h1>
118118
</select>
119119
<label for="theme_selector" class="mt-10">[[=z.token('settings.general.themePannel')]]:</label>
120120
<select name="themeSelector" id="theme_selector">
121-
<option value="dark">[[=z.token('package_info.navigation.dark')]]</option>
122-
<option value="light">[[=z.token('package_info.navigation.light')]]</option>
121+
<option value="dark">[[=z.token('settings.dark')]]</option>
122+
<option value="light">[[=z.token('settings.light')]]</option>
123+
</select>
124+
<label for="lang_selector" class="mt-10">[[=z.token('settings.general.langPannel')]]:</label>
125+
<select name="langSelector" id="lang_selector">
126+
<option value="french">[[=z.token('settings.languages.fr')]]</option>
127+
<option value="english">[[=z.token('settings.languages.en')]]</option>
123128
</select>
124129
<p class="settings-line-title">[[=z.token('settings.general.network')]]:</p>
125130
<div>

workspaces/cache/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fs from "node:fs";
55

66
// Import Third-party Dependencies
77
import cacache from "cacache";
8+
import * as i18n from "@nodesecure/i18n";
89

910
// Import Internal Dependencies
1011
import { logger } from "@nodesecure/server";
@@ -30,6 +31,7 @@ export interface AppConfig {
3031
};
3132
theme?: "light" | "dark";
3233
disableExternalRequests: boolean;
34+
lang?: i18n.languages;
3335
}
3436

3537
export interface PayloadsList {

workspaces/server/src/config.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Import Third-party Dependencies
22
import { warnings, type WarningName } from "@nodesecure/js-x-ray";
33
import { appCache, type AppConfig } from "@nodesecure/cache";
4+
import * as i18n from "@nodesecure/i18n";
45

56
// Import Internal Dependencies
67
import { logger } from "./logger.js";
@@ -16,6 +17,7 @@ const kDefaultConfig = {
1617
};
1718

1819
export async function get(): Promise<AppConfig> {
20+
const localLang = await i18n.getLocalLang();
1921
try {
2022
const config = await appCache.getConfig();
2123

@@ -26,7 +28,8 @@ export async function get(): Promise<AppConfig> {
2628
warnings = []
2729
} = {},
2830
theme,
29-
disableExternalRequests = false
31+
disableExternalRequests = false,
32+
lang = localLang
3033
} = config;
3134
logger.info(
3235
// eslint-disable-next-line @stylistic/max-len
@@ -40,7 +43,8 @@ export async function get(): Promise<AppConfig> {
4043
warnings
4144
},
4245
theme,
43-
disableExternalRequests
46+
disableExternalRequests,
47+
lang
4448
};
4549
}
4650
catch (err: any) {
@@ -50,7 +54,7 @@ export async function get(): Promise<AppConfig> {
5054

5155
logger.info(`[config|get](fallback to default: ${JSON.stringify(kDefaultConfig)})`);
5256

53-
return kDefaultConfig;
57+
return { ...kDefaultConfig, lang: localLang };
5458
}
5559
}
5660

@@ -66,4 +70,11 @@ export async function set(newValue: AppConfig) {
6670

6771
throw err;
6872
}
73+
74+
const i18nLocalLang = await i18n.getLocalLang();
75+
if (i18nLocalLang !== newValue.lang) {
76+
logger.info(`[config|set](updating i18n lang to: ${newValue.lang})`);
77+
await i18n.setLocalLang(newValue.lang!);
78+
await i18n.getLanguages();
79+
}
6980
}

workspaces/server/test/config.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import assert from "node:assert";
66
import cacache from "cacache";
77
import { warnings } from "@nodesecure/js-x-ray";
88
import { AppConfig, CACHE_PATH } from "@nodesecure/cache";
9+
import * as i18n from "@nodesecure/i18n";
910

1011
// Import Internal Dependencies
1112
import { get, set } from "../src/config.js";
@@ -17,6 +18,7 @@ describe("config", () => {
1718
let actualConfig: AppConfig;
1819

1920
before(async() => {
21+
await i18n.getLanguages();
2022
actualConfig = await get();
2123
});
2224

@@ -33,7 +35,8 @@ describe("config", () => {
3335
ignore: { flags: [], warnings: Object.entries(warnings)
3436
.filter(([_, { experimental }]) => experimental)
3537
.map(([warning]) => warning) },
36-
disableExternalRequests: false
38+
disableExternalRequests: false,
39+
lang: await i18n.getLocalLang()
3740
});
3841
});
3942

@@ -44,6 +47,7 @@ describe("config", () => {
4447
flags: ["foo"],
4548
warnings: ["bar"]
4649
},
50+
lang: "english",
4751
theme: "galaxy",
4852
disableExternalRequests: true
4953
};
@@ -60,6 +64,7 @@ describe("config", () => {
6064
flags: ["foz"],
6165
warnings: ["baz"]
6266
},
67+
lang: "english",
6368
theme: "galactic",
6469
disableExternalRequests: true
6570
};

workspaces/server/test/httpServer.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe("httpServer", { concurrency: 1 }, () => {
7979
const result = await get(kHttpURL);
8080

8181
assert.equal(result.statusCode, 200);
82-
assert.equal(result.headers["content-type"], "text/html");
82+
assert.ok(result.headers["content-type"]!.startsWith("text/html"));
8383
});
8484

8585
test("'/' should fail", async(ctx) => {
@@ -233,6 +233,7 @@ describe("httpServer", { concurrency: 1 }, () => {
233233
flags: ["foo"],
234234
warnings: ["bar"]
235235
},
236+
lang: "english",
236237
theme: "galaxy",
237238
disableExternalRequests: true
238239
};
@@ -250,19 +251,20 @@ describe("httpServer", { concurrency: 1 }, () => {
250251
});
251252

252253
test("PUT '/config' should update the config", async() => {
254+
const lang = await i18n.getLocalLang();
253255
const { data: actualConfig } = await get(new URL("/config", kHttpURL));
254256
// FIXME: use @mynusift/httpie instead of fetch. Atm it throws with put().
255257
// https://github.com/nodejs/undici/issues/583
256258
const { status } = await fetch(new URL("/config", kHttpURL), {
257259
method: "PUT",
258-
body: JSON.stringify({ fooz: "baz" }),
260+
body: JSON.stringify({ fooz: "baz", lang }),
259261
headers: { "Content-Type": "application/json" }
260262
});
261263

262264
assert.equal(status, 204);
263265

264266
const inCache = await cacache.get(CACHE_PATH, kConfigKey);
265-
assert.deepEqual(JSON.parse(inCache.data.toString()), { fooz: "baz" });
267+
assert.deepEqual(JSON.parse(inCache.data.toString()), { fooz: "baz", lang });
266268

267269
await fetch(new URL("/config", kHttpURL), {
268270
method: "PUT",

0 commit comments

Comments
 (0)