From 7b388b9acbab86f2958d39945332a8594c20ff1f Mon Sep 17 00:00:00 2001 From: Sainan Date: Thu, 6 Feb 2025 03:37:52 +0100 Subject: [PATCH] feat(webui): translations Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- package.json | 3 +- scripts/update-translations.js | 46 ++++++++ src/routes/webui.ts | 5 + static/webui/index.html | 200 ++++++++++++++------------------ static/webui/script.js | 139 ++++++++++++---------- static/webui/translations/en.js | 111 ++++++++++++++++++ static/webui/translations/ru.js | 112 ++++++++++++++++++ 7 files changed, 443 insertions(+), 173 deletions(-) create mode 100644 scripts/update-translations.js create mode 100644 static/webui/translations/en.js create mode 100644 static/webui/translations/ru.js diff --git a/package.json b/package.json index 28cac72a..1f6a0c04 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "build": "tsc && copyfiles static/webui/** build", "lint": "eslint --ext .ts .", "lint:fix": "eslint --fix --ext .ts .", - "prettier": "prettier --write ." + "prettier": "prettier --write .", + "update-translations": "cd scripts && node update-translations.js" }, "license": "GNU", "dependencies": { diff --git a/scripts/update-translations.js b/scripts/update-translations.js new file mode 100644 index 00000000..c26deb7f --- /dev/null +++ b/scripts/update-translations.js @@ -0,0 +1,46 @@ +// Based on http://209.141.38.3/OpenWF/Translations/src/branch/main/update.php +// Converted via ChatGPT-4o + +const fs = require('fs'); + +function extractStrings(content) { + const regex = /([a-zA-Z_]+): `([^`]*)`,/g; + let matches; + const strings = {}; + while ((matches = regex.exec(content)) !== null) { + strings[matches[1]] = matches[2]; + } + return strings; +} + +const source = fs.readFileSync("../static/webui/translations/en.js", "utf8"); +const sourceStrings = extractStrings(source); +const sourceLines = source.split("\n"); + +fs.readdirSync("../static/webui/translations").forEach(file => { + if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") { + const content = fs.readFileSync(`../static/webui/translations/${file}`, "utf8"); + const targetStrings = extractStrings(content); + const contentLines = content.split("\n"); + + const fileHandle = fs.openSync(`../static/webui/translations/${file}`, "w"); + fs.writeSync(fileHandle, contentLines[0] + "\n"); + + sourceLines.forEach(line => { + const strings = extractStrings(line); + if (Object.keys(strings).length > 0) { + Object.entries(strings).forEach(([key, value]) => { + if (targetStrings.hasOwnProperty(key)) { + fs.writeSync(fileHandle, `\t${key}: \`${targetStrings[key]}\`,\n`); + } else { + fs.writeSync(fileHandle, `\t${key}: \`[UNTRANSLATED] ${value}\`,\n`); + } + }); + } else { + fs.writeSync(fileHandle, line + "\n"); + } + }); + + fs.closeSync(fileHandle); + } +}); diff --git a/src/routes/webui.ts b/src/routes/webui.ts index 5ae72040..48f9f2fd 100644 --- a/src/routes/webui.ts +++ b/src/routes/webui.ts @@ -54,4 +54,9 @@ webuiRouter.get("/webui/riven-tool/RivenParser.js", (_req, res) => { res.sendFile(path.join(repoDir, "node_modules/warframe-riven-info/RivenParser.js")); }); +// Serve translations +webuiRouter.get("/translations/:file", (req, res) => { + res.sendFile(path.join(rootDir, `static/webui/translations/${req.params.file}`)); +}); + export { webuiRouter }; diff --git a/static/webui/index.html b/static/webui/index.html index 5448be04..c5fc1310 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -37,10 +37,10 @@ @@ -56,16 +56,16 @@ @@ -73,38 +73,35 @@
-

Login using your OpenWF account credentials (same as in-game when connecting to this server).

+

- +
- +
- +
-

- Note: Changes made here will only be reflected in-game when the game re-downloads your - inventory. Visiting the navigation should be the easiest way to trigger that. -

+

-
Add Items
+
- +
-
Warframes
+
- +
@@ -114,11 +111,11 @@
-
Primary Weapons
+
- +
@@ -130,11 +127,11 @@
-
Secondary Weapons
+
- +
@@ -144,11 +141,11 @@
-
Melee Weapons
+
- +
@@ -160,11 +157,11 @@
-
Archwing
+
- +
@@ -174,11 +171,11 @@
-
Archwing Primary Weapons
+
- +
@@ -190,11 +187,11 @@
-
Archwing Melee Weapons
+
- +
@@ -204,11 +201,11 @@
-
Necramechs
+
- +
@@ -220,11 +217,11 @@
-
Sentinels
+
- +
@@ -234,11 +231,11 @@
-
Sentinel Weapons
+
- +
@@ -250,7 +247,7 @@
-
Amps
+
@@ -260,7 +257,7 @@
-
K-Drives
+
@@ -270,23 +267,23 @@
-
Bulk Actions
+
- - - - - - + + + + + +
- - - - - - + + + + + +
@@ -295,14 +292,14 @@

-
Archon Shard Slots
+
-

You can use these unlimited slots to apply a wide range of upgrades.

+

x - +
@@ -311,14 +308,11 @@
-

- Note: Changes made here will only be reflected in-game when the game re-downloads your - inventory. Visiting the navigation should be the easiest way to trigger that. -

+

-
Add Riven
+
- - - Need help with the fingerprint? + + +
-
Rivens
+
@@ -345,12 +339,12 @@
-
Mods
+
- +
@@ -358,9 +352,9 @@
-
Bulk Actions
+
- +
@@ -373,127 +367,113 @@
Server
-

You must be an administrator to use this feature. To become an administrator, add "" to administratorNames in the config.json.

+

- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
-
Account
+
-

- +

+
-

You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.

+

- +
diff --git a/static/webui/script.js b/static/webui/script.js index e6332e20..45f8c9e8 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -14,6 +14,9 @@ function loginFromLocalStorage() { $(".displayname").text(data.DisplayName); window.accountId = data.id; window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce; + if (window.dict) { + updateLocElements(); + } updateInventory(); }, () => { @@ -50,7 +53,7 @@ function revalidateAuthz(succ_cb) { }, () => { logout(); - alert("Your credentials are no longer valid."); + alert(loc("code_nonValidAuthz")); single.loadRoute("/webui/"); // Show login screen } ); @@ -62,24 +65,17 @@ function logout() { } function renameAccount() { - const newname = window.prompt("What would you like to change your account name to?"); + const newname = window.prompt(loc("code_changeNameConfirm")); if (newname) { fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => { $(".displayname").text(newname); + updateLocElements(); }); } } function deleteAccount() { - if ( - window.confirm( - "Are you sure you want to delete your account " + - document.querySelector(".displayname").textContent + - " (" + - localStorage.getItem("email") + - ")? This action cannot be undone." - ) - ) { + if (window.confirm(loc("code_deleteAccountConfirm"))) { fetch("/custom/deleteAccount?" + window.authz).then(() => { logout(); single.loadRoute("/webui/"); // Show login screen @@ -110,12 +106,35 @@ single.on("route_load", function (event) { } }); +function loc(tag) { + return ((window.dict ?? {})[tag] ?? tag) + .split("|DISPLAYNAME|").join(document.querySelector(".displayname").textContent) + .split("|EMAIL|").join(localStorage.getItem("email")); +} + +function updateLocElements() { + document.querySelectorAll("[data-loc]").forEach(elm => { + elm.innerHTML = loc(elm.getAttribute("data-loc")); + }); +} + function setActiveLanguage(lang) { window.lang = lang; const lang_name = document.querySelector("[data-lang=" + lang + "]").textContent; document.getElementById("active-lang-name").textContent = lang_name; document.querySelector("[data-lang].active").classList.remove("active"); document.querySelector("[data-lang=" + lang + "]").classList.add("active"); + + window.dictPromise = new Promise(resolve => { + const webui_lang = ["en", "ru"].indexOf(lang) == -1 ? "en" : lang; + const script = document.createElement("script"); + script.src = "/translations/" + webui_lang + ".js"; + script.onload = function() { + updateLocElements(); + resolve(window.dict); + }; + document.documentElement.appendChild(script); + }); } setActiveLanguage(localStorage.getItem("lang") ?? "en"); @@ -130,35 +149,35 @@ let uniqueLevelCaps = {}; function fetchItemList() { window.itemListPromise = new Promise(resolve => { const req = $.get("/custom/getItemLists?lang=" + window.lang); - req.done(data => { + req.done(async (data) => { + await dictPromise; + window.archonCrystalUpgrades = data.archonCrystalUpgrades; const itemMap = { // Generics for rivens - "/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: "Archgun" }, - "/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: "Melee" }, - "/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" }, - "/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" }, - "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" }, + "/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: loc("code_archgun") }, + "/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: loc("code_melee") }, + "/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: loc("code_pistol") }, + "/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: loc("code_rifle") }, + "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: loc("code_shotgun") }, // Modular weapons - "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": { name: "Kitgun" }, - "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": { name: "Kitgun" }, - "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": { name: "Kitgun" }, - "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": { name: "Kitgun" }, - "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": { name: "Kitgun" }, - "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": { name: "Kitgun" }, - "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: "Kitgun" }, - "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: "Kitgun" }, - "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: "Zaw" }, - "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon": { - name: "Mote Amp" - }, - "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": { name: "Amp" }, - "/Lotus/Weapons/Operator/Pistols/DrifterPistol/DrifterPistolPlayerWeapon": { name: "Sirocco" }, - "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": { name: "K-Drive" }, + "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": { name: loc("code_kitgun") }, + "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": { name: loc("code_kitgun") }, + "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": { name: loc("code_kitgun") }, + "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": { name: loc("code_kitgun") }, + "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": { name: loc("code_kitgun") }, + "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": { name: loc("code_kitgun") }, + "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: loc("code_kitgun") }, + "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: loc("code_kitgun") }, + "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: loc("code_zaw") }, + "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon": { name: loc("code_moteAmp") }, + "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": { name: loc("code_amp") }, + "/Lotus/Weapons/Operator/Pistols/DrifterPistol/DrifterPistolPlayerWeapon": { name: loc("code_sirocco") }, + "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": { name: loc("code_kdrive") }, // Missing in data sources - "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser": { name: "Legendary Core" }, - "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" } + "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser": { name: loc("code_legendaryCore") }, + "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: loc("code_traumaticPeculiar") } }; for (const [type, items] of Object.entries(data)) { if (type == "archonCrystalUpgrades") { @@ -173,7 +192,7 @@ function fetchItemList() { } else if (type != "badItems") { items.forEach(item => { if (item.uniqueName in data.badItems) { - item.name += " (Imposter)"; + item.name += " " + loc("code_badItem"); } else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") { const option = document.createElement("option"); option.setAttribute("data-key", item.uniqueName); @@ -272,7 +291,7 @@ function updateInventory() { } } }; - a.title = "Max Rank"; + a.title = loc("code_maxRank"); a.innerHTML = ``; td.appendChild(a); } @@ -287,12 +306,12 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - const name = prompt("Enter new custom name:"); + const name = prompt(loc("code_renamePrompt")); if (name !== null) { renameGear(category, item.ItemId.$oid, name); } }; - a.title = "Rename"; + a.title = loc("code_rename"); a.innerHTML = ``; td.appendChild(a); } @@ -303,7 +322,7 @@ function updateInventory() { event.preventDefault(); disposeOfGear(category, item.ItemId.$oid); }; - a.title = "Remove"; + a.title = loc("code_remove"); a.innerHTML = ``; td.appendChild(a); } @@ -327,11 +346,11 @@ function updateInventory() { const td = document.createElement("td"); td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat; td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name; - td.innerHTML += " ▲ " + fingerprint.buffs.length + ""; + td.innerHTML += " ▲ " + fingerprint.buffs.length + ""; td.innerHTML += - " ▼ " + fingerprint.curses.length + ""; + " ▼ " + fingerprint.curses.length + ""; td.innerHTML += - " ⟳ " + parseInt(fingerprint.rerolls) + ""; + " ⟳ " + parseInt(fingerprint.rerolls) + ""; tr.appendChild(td); } { @@ -349,7 +368,7 @@ function updateInventory() { }) ); a.target = "_blank"; - a.title = "View Stats"; + a.title = loc("code_viewStats"); a.innerHTML = ``; td.appendChild(a); } @@ -360,7 +379,7 @@ function updateInventory() { event.preventDefault(); disposeOfGear("Upgrades", item.ItemId.$oid); }; - a.title = "Remove"; + a.title = loc("code_remove"); a.innerHTML = ``; td.appendChild(a); } @@ -376,7 +395,7 @@ function updateInventory() { { const td = document.createElement("td"); td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; - td.innerHTML += " ★ " + rank + "/" + maxRank + ""; + td.innerHTML += " ★ " + rank + "/" + maxRank + ""; tr.appendChild(td); } { @@ -389,7 +408,7 @@ function updateInventory() { event.preventDefault(); setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank }); }; - a.title = "Max Rank"; + a.title = loc("code_maxRank"); a.innerHTML = ``; td.appendChild(a); } @@ -400,7 +419,7 @@ function updateInventory() { event.preventDefault(); disposeOfGear("Upgrades", item.ItemId.$oid); }; - a.title = "Remove"; + a.title = loc("code_remove"); a.innerHTML = ``; td.appendChild(a); } @@ -415,7 +434,7 @@ function updateInventory() { { const td = document.createElement("td"); td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; - td.innerHTML += " ★ 0/" + maxRank + ""; + td.innerHTML += " ★ 0/" + maxRank + ""; if (item.ItemCount > 1) { td.innerHTML += " 🗍 " + parseInt(item.ItemCount) + ""; } @@ -431,7 +450,7 @@ function updateInventory() { event.preventDefault(); setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank }); }; - a.title = "Max Rank"; + a.title = loc("code_maxRank"); a.innerHTML = ``; td.appendChild(a); } @@ -442,7 +461,7 @@ function updateInventory() { event.preventDefault(); disposeOfItems("Upgrades", item.ItemType, item.ItemCount); }; - a.title = "Remove"; + a.title = loc("code_remove"); a.innerHTML = ``; td.appendChild(a); } @@ -491,7 +510,7 @@ function updateInventory() { event.preventDefault(); doPopArchonCrystalUpgrade(upgradeType); }; - a.title = "Remove"; + a.title = loc("code_remove"); a.innerHTML = ``; td.appendChild(a); } @@ -572,7 +591,7 @@ function addMissingEquipment(categories) { }); if ( requests.length != 0 && - window.confirm("Are you sure you want to add " + requests.length + " items to your account?") + window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length)) ) { dispatchAddItemsRequestsBatch(requests); } @@ -627,7 +646,7 @@ function maxRankAllEquipment(categories) { return sendBatchGearExp(batchData); } - alert("No equipment to rank up."); + alert(loc("code_noEquipmentToRankUp")); }); }); } @@ -743,7 +762,7 @@ function doAcquireMiscItems() { } ]) }).done(function () { - alert("Successfully added."); + alert(loc("code_succAdded")); }); }); } @@ -938,13 +957,9 @@ function doUnlockAllFocusSchools() { await unlockFocusSchool(upgradeType); } if (Object.keys(missingFocusUpgrades).length == 0) { - alert("All focus schools are already unlocked."); + alert(loc("code_focusAllUnlocked")); } else { - alert( - "Unlocked " + - Object.keys(missingFocusUpgrades).length + - " new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that." - ); + alert(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length)); } }); }); @@ -996,7 +1011,7 @@ function doAddAllMods() { modsAll = Array.from(modsAll); if ( modsAll.length != 0 && - window.confirm("Are you sure you want to add " + modsAll.length + " mods to your account?") + window.confirm(loc("code_addModsConfirm").split("|COUNT|").join(modsAll.length)) ) { $.post({ url: "/custom/addItems?" + window.authz, @@ -1071,7 +1086,7 @@ function doImport() { inventory: JSON.parse($("#import-inventory").val()) }) }).then(function () { - alert("Successfully imported."); + alert(loc("code_succImport")); updateInventory(); }); }); diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js new file mode 100644 index 00000000..134a91a9 --- /dev/null +++ b/static/webui/translations/en.js @@ -0,0 +1,111 @@ +dict = { + general_inventoryUpdateNote: `Note: Changes made here will only be reflected in-game when the game re-downloads your inventory. Visiting the navigation should be the easiest way to trigger that.`, + general_addButton: `Add`, + general_bulkActions: `Bulk Actions`, + code_nonValidAuthz: `Your credentials are no longer valid.`, + code_changeNameConfirm: `What would you like to change your account name to?`, + code_deleteAccountConfirm: `Are you sure you want to delete your account |DISPLAYNAME| (|EMAIL|)? This action cannot be undone.`, + code_archgun: `Archgun`, + code_melee: `Melee`, + code_pistol: `Pistol`, + code_rifle: `Rifle`, + code_shotgun: `Shotgun`, + code_kitgun: `Kitgun`, + code_zaw: `Zaw`, + code_moteAmp: `Mote Amp`, + code_amp: `Amp`, + code_sirocco: `Sirocco`, + code_kDrive: `K-Drive`, + code_legendaryCore: `Legendary Core`, + code_traumaticPeculiar: `Traumatic Peculiar`, + code_badItem: `(Imposter)`, + code_maxRank: `Max Rank`, + code_rename: `Rename`, + code_renamePrompt: `Enter new custom name:`, + code_remove: `Remove`, + code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`, + code_noEquipmentToRankUp: `No equipment to rank up.`, + code_succAdded: `Successfully added.`, + code_buffsNumber: `Number of buffs`, + code_cursesNumber: `Number of curses`, + code_rerollsNumber: `Number of rerolls`, + code_viewStats: `View Stats`, + code_rank: `Rank`, + code_count: `Count`, + code_focusAllUnlocked: `All focus schools are already unlocked.`, + code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, + code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`, + code_succImport: `Successfully imported.`, + login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`, + login_emailLabel: `Email address`, + login_passwordLabel: `Password`, + login_loginButton: `Login`, + navbar_logout: `Logout`, + navbar_renameAccount: `Rename Account`, + navbar_deleteAccount: `Delete Account`, + navbar_inventory: `Inventory`, + navbar_mods: `Mods`, + navbar_cheats: `Cheats`, + navbar_import: `Import`, + inventory_addItems: `Add Items`, + inventory_suits: `Warframes`, + inventory_longGuns: `Primary Weapons`, + inventory_pistols: `Secondary Weapons`, + inventory_melee: `Melee Weapons`, + inventory_spaceSuits: `Archwings`, + inventory_spaceGuns: `Archwing Primary Weapons`, + inventory_spaceMelee: `Archwing Melee Weapons`, + inventory_mechSuits: `Necramechs`, + inventory_sentinels: `Sentinels`, + inventory_sentinelWeapons: `Sentinel Weapons`, + inventory_operatorAmps: `Amps`, + inventory_hoverboards: `K-Drives`, + inventory_bulkAddSuits: `Add Missing Warframes`, + inventory_bulkAddWeapons: `Add Missing Weapons`, + inventory_bulkAddSpaceSuits: `Add Missing Archwings`, + inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`, + inventory_bulkAddSentinels: `Add Missing Sentinels`, + inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`, + inventory_bulkRankUpSuits: `Max Rank All Warframes`, + inventory_bulkRankUpWeapons: `Max Rank All Weapons`, + inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, + inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`, + inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, + inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, + powersuit_archonShardsLabel: `Archon Shard Slots`, + powersuit_archonShardsDescription: `You can use these unlimited slots to apply a wide range of upgrades`, + mods_addRiven: `Add Riven`, + mods_fingerprint: `Fingerprint`, + mods_fingerprintHelp: `Need help with the fingerprint?`, + mods_rivens: `Rivens`, + mods_mods: `Mods`, + mods_bulkAddMods: `Add Missing Mods`, + cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add |DISPLAYNAME| to administratorNames in the config.json.`, + cheats_skipTutorial: `Skip Tutorial`, + cheats_skipAllDialogue: `Skip All Dialogue`, + cheats_unlockAllScans: `Unlock All Scans`, + cheats_unlockAllMissions: `Unlock All Missions`, + cheats_unlockAllQuests: `Unlock All Quests`, + cheats_completeAllQuests: `Complete All Quests`, + cheats_infiniteCredits: `Infinite Credits`, + cheats_infinitePlatinum: `Infinite Platinum`, + cheats_infiniteEndo: `Infinite Endo`, + cheats_infiniteRegalAya: `Infinite Regal Aya`, + cheats_unlockAllShipFeatures: `Unlock All Ship Features`, + cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`, + cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, + cheats_unlockAllSkins: `Unlock All Skins`, + cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`, + cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`, + cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`, + cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`, + cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`, + cheats_noDailyStandingLimits: `No Daily Standing Limits`, + cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, + cheats_saveSettings: `Save Settings`, + cheats_account: `Account`, + cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, + cheats_helminthUnlockAll: `Fully Level Up Helminth`, + import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.`, + import_submit: `Submit`, +} \ No newline at end of file diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js new file mode 100644 index 00000000..acee9501 --- /dev/null +++ b/static/webui/translations/ru.js @@ -0,0 +1,112 @@ +// Russian translation by AMelonInsideLemon +dict = { + general_inventoryUpdateNote: `Примечание: изменения, внесенные здесь, отобразятся в игре только после повторной загрузки вашего инвентаря. Посещение навигации — самый простой способ этого добиться.`, + general_addButton: `Добавить`, + general_bulkActions: `Массовые действия`, + code_nonValidAuthz: `Ваши данные больше не действительны.`, + code_changeNameConfirm: `Какое имя вы хотите установить для своей учетной записи?`, + code_deleteAccountConfirm: `Вы уверены, что хотите удалить аккаунт |DISPLAYNAME| (|EMAIL|)? Это действие нельзя отменить.`, + code_archgun: `Арч-Пушка`, + code_melee: `Ближний бой`, + code_pistol: `Пистолет`, + code_rifle: `Винтовка`, + code_shotgun: `Дробовик`, + code_kitgun: `Китган`, + code_zaw: `Зо`, + code_moteAmp: `Пылинка`, + code_amp: `Усилитель`, + code_sirocco: `Сирокко`, + code_kDrive: `К-Драйв`, + code_legendaryCore: `Легендарное ядро`, + code_traumaticPeculiar: `Травмирующая Странность`, + code_badItem: `(Самозванец)`, + code_maxRank: `Максимальный ранг`, + code_rename: `Переименовать`, + code_renamePrompt: `Введите новое имя:`, + code_remove: `Удалить`, + code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`, + code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`, + code_succAdded: `Успешно добавлено.`, + code_buffsNumber: `Количество усилений`, + code_cursesNumber: `Количество проклятий`, + code_rerollsNumber: `Количество циклов`, + code_viewStats: `Просмотр характеристики`, + code_rank: `Ранг`, + code_count: `Количество`, + code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`, + code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, + code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`, + code_succImport: `Успешно импортировано.`, + login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, + login_emailLabel: `Адрес электронной почты`, + login_passwordLabel: `Пароль`, + login_loginButton: `Войти`, + navbar_logout: `Выйти`, + navbar_renameAccount: `Переименовать аккаунт`, + navbar_deleteAccount: `Удалить аккаунт`, + navbar_inventory: `Инвентарь`, + navbar_mods: `Моды`, + navbar_cheats: `Читы`, + navbar_import: `Импорт`, + inventory_addItems: `Добавить предметы`, + inventory_suits: `Варфреймы`, + inventory_longGuns: `Основное оружие`, + inventory_pistols: `Вторичное оружие`, + inventory_melee: `Оружие ближнего боя`, + inventory_spaceSuits: `Арчвинги`, + inventory_spaceGuns: `Оружие арчвинга`, + inventory_spaceMelee: `Оружие ближнего боя арчвинга`, + inventory_mechSuits: `Некрамехи`, + inventory_sentinels: `Стражи`, + inventory_sentinelWeapons: `Оружие стражей`, + inventory_operatorAmps: `Усилители`, + inventory_hoverboards: `К-Драйвы`, + inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`, + inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, + inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`, + inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`, + inventory_bulkAddSentinels: `Добавить отсутствующих стражей`, + inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`, + inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`, + inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`, + inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`, + inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`, + inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, + inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, + powersuit_archonShardsLabel: `Ячейки осколков архонта`, + powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`, + mods_addRiven: `Добавить Мод Разлома`, + mods_fingerprint: `Отпечаток`, + mods_fingerprintHelp: `Нужна помощь с отпечатком?`, + mods_rivens: `Моды Разлома`, + mods_mods: `Моды`, + mods_bulkAddMods: `Добавить отсутствующие моды`, + cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте \"|DISPLAYNAME|\" в administratorNames в config.json.`, + cheats_skipTutorial: `Пропустить обучение`, + cheats_skipAllDialogue: `Пропустить все диалоги`, + cheats_unlockAllScans: `Разблокировать все сканирования`, + cheats_unlockAllMissions: `Разблокировать все миссии`, + cheats_unlockAllQuests: `Разблокировать все квесты`, + cheats_completeAllQuests: `Завершить все квесты`, + cheats_infiniteCredits: `Бесконечные кредиты`, + cheats_infinitePlatinum: `Бесконечный платина`, + cheats_infiniteEndo: `Бесконечное эндо`, + cheats_infiniteRegalAya: `Бесконечная Королевская Айя`, + cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, + cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`, + cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, + cheats_unlockAllSkins: `Разблокировать все скины`, + cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`, + cheats_universalPolarityEverywhere: `Универсальная полярность везде`, + cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`, + cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`, + cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`, + cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`, + cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, + cheats_saveSettings: `Сохранить настройки`, + cheats_account: `Аккаунт`, + cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, + cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, + import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, + import_submit: `Отправить`, +}