返回提交历史
Modified
src/controllers/custom/getItemListsController.ts
+10
-9
Modified
src/services/itemDataService.ts
+50
-2
Modified
static/webui/index.html
+29
-7
Modified
static/webui/script.js
+61
-42
XFEstudio/SpaceNinjaServer
feat(webui): language selector (#593)
0a31ff7b
代码差异
4 个文件
+150
-60
@@ -1,5 +1,5 @@
1
1
import { RequestHandler } from "express";
2
import { getEnglishString } from "@/src/services/itemDataService";
2
import { getDict, getString } from "@/src/services/itemDataService";
3
3
import {
4
4
ExportArcanes,
5
5
ExportGear,
@@ -16,7 +16,8 @@ interface ListedItem {
16
16
fusionLimit?: number;
17
17
}
18
18
19
const getItemListsController: RequestHandler = (_req, res) => {
19
const getItemListsController: RequestHandler = (req, res) => {
20
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
20
21
const weapons = [];
21
22
const miscitems = [];
22
23
for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
@@ -24,12 +25,12 @@ const getItemListsController: RequestHandler = (_req, res) => {
24
25
if (item.totalDamage !== 0) {
25
26
weapons.push({
26
27
uniqueName,
27
name: getEnglishString(item.name)
28
name: getString(item.name, lang)
28
29
});
29
30
} else if (!item.excludeFromCodex) {
30
31
miscitems.push({
31
32
uniqueName: "MiscItems:" + uniqueName,
32
name: getEnglishString(item.name)
33
name: getString(item.name, lang)
33
34
});
34
35
}
35
36
}
@@ -37,13 +38,13 @@ const getItemListsController: RequestHandler = (_req, res) => {
37
38
for (const [uniqueName, item] of Object.entries(ExportResources)) {
38
39
miscitems.push({
39
40
uniqueName: item.productCategory + ":" + uniqueName,
40
name: getEnglishString(item.name)
41
name: getString(item.name, lang)
41
42
});
42
43
}
43
44
for (const [uniqueName, item] of Object.entries(ExportGear)) {
44
45
miscitems.push({
45
46
uniqueName: "Consumables:" + uniqueName,
46
name: getEnglishString(item.name)
47
name: getString(item.name, lang)
47
48
});
48
49
}
49
50
@@ -52,7 +53,7 @@ const getItemListsController: RequestHandler = (_req, res) => {
52
53
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
53
54
mods.push({
54
55
uniqueName,
55
name: getEnglishString(upgrade.name),
56
name: getString(upgrade.name, lang),
56
57
fusionLimit: upgrade.fusionLimit
57
58
});
58
59
if (upgrade.isStarter || upgrade.isFrivolous || upgrade.upgradeEntries) {
@@ -62,7 +63,7 @@ const getItemListsController: RequestHandler = (_req, res) => {
62
63
for (const [uniqueName, arcane] of Object.entries(ExportArcanes)) {
63
64
mods.push({
64
65
uniqueName,
65
name: getEnglishString(arcane.name)
66
name: getString(arcane.name, lang)
66
67
});
67
68
if (arcane.isFrivolous) {
68
69
badItems[uniqueName] = true;
@@ -75,7 +76,7 @@ const getItemListsController: RequestHandler = (_req, res) => {
75
76
.map(([uniqueName, warframe]) => {
76
77
return {
77
78
uniqueName,
78
name: getEnglishString(warframe.name)
79
name: getString(warframe.name, lang)
79
80
};
80
81
}),
81
82
weapons,
@@ -1,7 +1,21 @@
1
1
import { getIndexAfter } from "@/src/helpers/stringHelpers";
2
2
import { logger } from "@/src/utils/logger";
3
3
import {
4
dict_de,
4
5
dict_en,
6
dict_es,
7
dict_fr,
8
dict_it,
9
dict_ja,
10
dict_ko,
11
dict_pl,
12
dict_pt,
13
dict_ru,
14
dict_tc,
15
dict_th,
16
dict_tr,
17
dict_uk,
18
dict_zh,
5
19
ExportRecipes,
6
20
ExportWarframes,
7
21
ExportWeapons,
@@ -70,6 +84,40 @@ export const getSuitByUniqueName = (uniqueName: string): IPowersuit | undefined
70
84
return ExportWarframes[uniqueName];
71
85
};
72
86
73
export const getEnglishString = (key: string): string => {
74
return dict_en[key] ?? key;
87
export const getDict = (lang: string): Record<string, string> => {
88
switch (lang) {
89
case "de":
90
return dict_de;
91
case "es":
92
return dict_es;
93
case "fr":
94
return dict_fr;
95
case "it":
96
return dict_it;
97
case "ja":
98
return dict_ja;
99
case "ko":
100
return dict_ko;
101
case "pl":
102
return dict_pl;
103
case "pt":
104
return dict_pt;
105
case "ru":
106
return dict_ru;
107
case "tc":
108
return dict_tc;
109
case "th":
110
return dict_th;
111
case "tr":
112
return dict_tr;
113
case "uk":
114
return dict_uk;
115
case "zh":
116
return dict_zh;
117
}
118
return dict_en;
119
};
120
121
export const getString = (key: string, dict: Record<string, string>): string => {
122
return dict[key] ?? key;
75
123
};
@@ -7,18 +7,40 @@
7
7
<link rel="stylesheet" href="/webui/style.css" />
8
8
</head>
9
9
<body>
10
<nav class="navbar sticky-top bg-body-tertiary">
10
<nav class="navbar navbar-expand sticky-top bg-body-tertiary">
11
11
<div class="container">
12
12
<button class="navbar-toggler d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebar" aria-controls="sidebar" aria-label="Toggle sidebar">
13
13
<span class="navbar-toggler-icon"></span>
14
14
</button>
15
15
<a class="navbar-brand">OpenWF WebUI</a>
16
<div class="nav-item dropdown">
17
<button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
18
<ul class="dropdown-menu dropdown-menu-end">
19
<li><a class="dropdown-item" href="/webui/" onclick="logout();">Logout</a></li>
20
</ul>
21
</div>
16
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
17
<li class="nav-item dropdown">
18
<button id="active-lang-name" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"></button>
19
<ul class="dropdown-menu dropdown-menu-end">
20
<li><a class="dropdown-item active" href="#" data-lang="en" onclick="event.preventDefault();setLanguage('en');">English</a></li>
21
<li><a class="dropdown-item" href="#" data-lang="de" onclick="event.preventDefault();setLanguage('de');">Deutsch</a></li>
22
<li><a class="dropdown-item" href="#" data-lang="es" onclick="event.preventDefault();setLanguage('es');">Español</a></li>
23
<li><a class="dropdown-item" href="#" data-lang="fr" onclick="event.preventDefault();setLanguage('fr');">Français</a></li>
24
<li><a class="dropdown-item" href="#" data-lang="it" onclick="event.preventDefault();setLanguage('it');">Italiano</a></li>
25
<li><a class="dropdown-item" href="#" data-lang="ja" onclick="event.preventDefault();setLanguage('ja');">日本語</a></li>
26
<li><a class="dropdown-item" href="#" data-lang="ko" onclick="event.preventDefault();setLanguage('ko');">한국어</a></li>
27
<li><a class="dropdown-item" href="#" data-lang="pl" onclick="event.preventDefault();setLanguage('pl');">Polski</a></li>
28
<li><a class="dropdown-item" href="#" data-lang="pt" onclick="event.preventDefault();setLanguage('pt');">Português</a></li>
29
<li><a class="dropdown-item" href="#" data-lang="ru" onclick="event.preventDefault();setLanguage('ru');">Русский</a></li>
30
<li><a class="dropdown-item" href="#" data-lang="tr" onclick="event.preventDefault();setLanguage('tr');">Türkçe</a></li>
31
<li><a class="dropdown-item" href="#" data-lang="uk" onclick="event.preventDefault();setLanguage('uk');">Українська</a></li>
32
<li><a class="dropdown-item" href="#" data-lang="zh" onclick="event.preventDefault();setLanguage('zh');">简体中文</a></li>
33
<li><a class="dropdown-item" href="#" data-lang="tc" onclick="event.preventDefault();setLanguage('tc');">繁體中文</a></li>
34
<li><a class="dropdown-item" href="#" data-lang="th" onclick="event.preventDefault();setLanguage('th');">แบบไทย</a></li>
35
</ul>
36
</li>
37
<li class="nav-item dropdown">
38
<button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
39
<ul class="dropdown-menu dropdown-menu-end">
40
<li><a class="dropdown-item" href="/webui/" onclick="logout();">Logout</a></li>
41
</ul>
42
</li>
43
</ul>
22
44
</div>
23
45
</nav>
24
46
<div class="container pt-3 pb-3" id="main-view">
@@ -84,52 +84,71 @@ single.on("route_load", function (event) {
84
84
}
85
85
});
86
86
87
window.itemListPromise = new Promise(resolve => {
88
const req = $.get("/custom/getItemLists");
89
req.done(data => {
90
window.archonCrystalUpgrades = data.archonCrystalUpgrades;
91
92
const itemMap = {
93
// Generics for rivens
94
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: "Archgun" },
95
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: "Melee" },
96
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" },
97
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" },
98
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" },
99
// Modular weapons
100
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": { name: "Kitgun" },
101
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": { name: "Kitgun" },
102
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: "Kitgun" },
103
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: "Kitgun" },
104
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: "Zaw" },
105
// Missing in data sources
106
"/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" }
107
};
108
for (const [type, items] of Object.entries(data)) {
109
if (type == "archonCrystalUpgrades") {
110
Object.entries(items).forEach(([uniqueName, name]) => {
111
const option = document.createElement("option");
112
option.setAttribute("data-key", uniqueName);
113
option.value = name;
114
document.getElementById("datalist-" + type).appendChild(option);
115
});
116
} else if (type != "badItems") {
117
items.forEach(item => {
118
if (item.uniqueName in data.badItems) {
119
item.name += " (Imposter)";
120
} else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") {
87
function setActiveLanguage(lang) {
88
window.lang = lang;
89
const lang_name = document.querySelector("[data-lang=" + lang + "]").textContent;
90
document.getElementById("active-lang-name").textContent = lang_name;
91
document.querySelector("[data-lang].active").classList.remove("active");
92
document.querySelector("[data-lang=" + lang + "]").classList.add("active");
93
}
94
setActiveLanguage(localStorage.getItem("lang") ?? "en");
95
96
function setLanguage(lang) {
97
setActiveLanguage(lang);
98
localStorage.setItem("lang", lang);
99
fetchItemList();
100
updateInventory();
101
}
102
103
function fetchItemList() {
104
window.itemListPromise = new Promise(resolve => {
105
const req = $.get("/custom/getItemLists?lang=" + window.lang);
106
req.done(data => {
107
window.archonCrystalUpgrades = data.archonCrystalUpgrades;
108
109
const itemMap = {
110
// Generics for rivens
111
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: "Archgun" },
112
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: "Melee" },
113
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" },
114
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" },
115
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" },
116
// Modular weapons
117
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": { name: "Kitgun" },
118
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": { name: "Kitgun" },
119
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: "Kitgun" },
120
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: "Kitgun" },
121
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: "Zaw" },
122
// Missing in data sources
123
"/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" }
124
};
125
for (const [type, items] of Object.entries(data)) {
126
if (type == "archonCrystalUpgrades") {
127
Object.entries(items).forEach(([uniqueName, name]) => {
121
128
const option = document.createElement("option");
122
option.setAttribute("data-key", item.uniqueName);
123
option.value = item.name;
129
option.setAttribute("data-key", uniqueName);
130
option.value = name;
124
131
document.getElementById("datalist-" + type).appendChild(option);
125
}
126
itemMap[item.uniqueName] = { ...item, type };
127
});
132
});
133
} else if (type != "badItems") {
134
items.forEach(item => {
135
if (item.uniqueName in data.badItems) {
136
item.name += " (Imposter)";
137
} else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") {
138
const option = document.createElement("option");
139
option.setAttribute("data-key", item.uniqueName);
140
option.value = item.name;
141
document.getElementById("datalist-" + type).appendChild(option);
142
}
143
itemMap[item.uniqueName] = { ...item, type };
144
});
145
}
128
146
}
129
}
130
resolve(itemMap);
147
resolve(itemMap);
148
});
131
149
});
132
});
150
}
151
fetchItemList();
133
152
134
153
function updateInventory() {
135
154
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");