From 3f6734ac1c7e001240726282c9018c80cded12e9 Mon Sep 17 00:00:00 2001
From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Date: Fri, 25 Apr 2025 12:00:38 -0700
Subject: [PATCH] feat(webui): EvolutionProgress support (#1818)
Closes #1815
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1818
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
---
 .../custom/getItemListsController.ts          |   9 +
 .../custom/setEvolutionProgressController.ts  |  33 ++++
 src/routes/custom.ts                          |   4 +-
 static/fixed_responses/allIncarnonList.json   |  49 ++++++
 static/webui/index.html                       |  19 +++
 static/webui/script.js                        | 157 +++++++++++++++++-
 static/webui/translations/de.js               |   5 +
 static/webui/translations/en.js               |   5 +
 static/webui/translations/es.js               |   5 +
 static/webui/translations/fr.js               |   5 +
 static/webui/translations/ru.js               |   5 +
 static/webui/translations/zh.js               |   5 +
 12 files changed, 293 insertions(+), 8 deletions(-)
 create mode 100644 src/controllers/custom/setEvolutionProgressController.ts
 create mode 100644 static/fixed_responses/allIncarnonList.json
diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts
index 38c304f7..716104a5 100644
--- a/src/controllers/custom/getItemListsController.ts
+++ b/src/controllers/custom/getItemListsController.ts
@@ -20,6 +20,7 @@ import {
     TRelicQuality
 } from "warframe-public-export-plus";
 import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
+import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
 
 interface ListedItem {
     uniqueName: string;
@@ -51,6 +52,7 @@ interface ItemLists {
     OperatorAmps: ListedItem[];
     QuestKeys: ListedItem[];
     KubrowPets: ListedItem[];
+    EvolutionProgress: ListedItem[];
     mods: ListedItem[];
 }
 
@@ -82,6 +84,7 @@ const getItemListsController: RequestHandler = (req, response) => {
         OperatorAmps: [],
         QuestKeys: [],
         KubrowPets: [],
+        EvolutionProgress: [],
         mods: []
     };
     for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
@@ -283,6 +286,12 @@ const getItemListsController: RequestHandler = (req, response) => {
             });
         }
     }
+    for (const uniqueName of allIncarnons) {
+        res.EvolutionProgress.push({
+            uniqueName,
+            name: getString(getItemName(uniqueName) || "", lang)
+        });
+    }
 
     response.json(res);
 };
diff --git a/src/controllers/custom/setEvolutionProgressController.ts b/src/controllers/custom/setEvolutionProgressController.ts
new file mode 100644
index 00000000..45c7c68b
--- /dev/null
+++ b/src/controllers/custom/setEvolutionProgressController.ts
@@ -0,0 +1,33 @@
+import { getInventory } from "@/src/services/inventoryService";
+import { getAccountIdForRequest } from "@/src/services/loginService";
+import { RequestHandler } from "express";
+
+export const setEvolutionProgressController: RequestHandler = async (req, res) => {
+    const accountId = await getAccountIdForRequest(req);
+    const inventory = await getInventory(accountId);
+    const payload = req.body as ISetEvolutionProgressRequest;
+
+    inventory.EvolutionProgress ??= [];
+    payload.forEach(element => {
+        const entry = inventory.EvolutionProgress!.find(entry => entry.ItemType === element.ItemType);
+
+        if (entry) {
+            entry.Progress = 0;
+            entry.Rank = element.Rank;
+        } else {
+            inventory.EvolutionProgress!.push({
+                Progress: 0,
+                Rank: element.Rank,
+                ItemType: element.ItemType
+            });
+        }
+    });
+
+    await inventory.save();
+    res.end();
+};
+
+type ISetEvolutionProgressRequest = {
+    ItemType: string;
+    Rank: number;
+}[];
diff --git a/src/routes/custom.ts b/src/routes/custom.ts
index fbd859c1..e555ef46 100644
--- a/src/routes/custom.ts
+++ b/src/routes/custom.ts
@@ -17,10 +17,11 @@ import { addCurrencyController } from "@/src/controllers/custom/addCurrencyContr
 import { addItemsController } from "@/src/controllers/custom/addItemsController";
 import { addXpController } from "@/src/controllers/custom/addXpController";
 import { importController } from "@/src/controllers/custom/importController";
+import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
+import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
 
 import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
 import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
-import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
 
 const customRouter = express.Router();
 
@@ -42,6 +43,7 @@ customRouter.post("/addItems", addItemsController);
 customRouter.post("/addXp", addXpController);
 customRouter.post("/import", importController);
 customRouter.post("/manageQuests", manageQuestsController);
+customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
 
 customRouter.get("/config", getConfigDataController);
 customRouter.post("/config", updateConfigDataController);
diff --git a/static/fixed_responses/allIncarnonList.json b/static/fixed_responses/allIncarnonList.json
new file mode 100644
index 00000000..3d85db63
--- /dev/null
+++ b/static/fixed_responses/allIncarnonList.json
@@ -0,0 +1,49 @@
+[
+  "/Lotus/Weapons/ClanTech/Bio/BioWeapon",
+  "/Lotus/Weapons/ClanTech/Energy/EnergyRifle",
+  "/Lotus/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun",
+  "/Lotus/Weapons/Corpus/Pistols/CrpHandRL/CorpusHandRocketLauncher",
+  "/Lotus/Weapons/Grineer/LongGuns/GrineerSawbladeGun/SawBladeGun",
+  "/Lotus/Weapons/Grineer/Melee/GrineerTylAxeAndBoar/RegorAxeShield",
+  "/Lotus/Weapons/Grineer/Pistols/HeatGun/GrnHeatGun",
+  "/Lotus/Weapons/Infested/Pistols/InfVomitGun/InfVomitGunWep",
+  "/Lotus/Weapons/Syndicates/CephalonSuda/Pistols/CSDroidArray",
+  "/Lotus/Weapons/Tenno/Bows/HuntingBow",
+  "/Lotus/Weapons/Tenno/Bows/StalkerBow",
+  "/Lotus/Weapons/Tenno/LongGuns/TnoLeverAction/TnoLeverActionRifle",
+  "/Lotus/Weapons/Tenno/Melee/Axe/DualInfestedAxesWeapon",
+  "/Lotus/Weapons/Tenno/Melee/Dagger/CeramicDagger",
+  "/Lotus/Weapons/Tenno/Melee/Fist/Fist",
+  "/Lotus/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer",
+  "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword",
+  "/Lotus/Weapons/Tenno/Melee/Maces/PaladinMace/PaladinMaceWeapon",
+  "/Lotus/Weapons/Tenno/Melee/Scythe/StalkerScytheWeapon",
+  "/Lotus/Weapons/Tenno/Melee/Scythe/ParisScythe/ParisScythe",
+  "/Lotus/Weapons/Tenno/Melee/Staff/Staff",
+  "/Lotus/Weapons/Tenno/Melee/Swords/CutlassAndPoignard/TennoCutlass",
+  "/Lotus/Weapons/Tenno/Melee/Swords/TennoSai/TennoSais",
+  "/Lotus/Weapons/Tenno/Pistol/AutoPistol",
+  "/Lotus/Weapons/Tenno/Pistol/BurstPistol",
+  "/Lotus/Weapons/Tenno/Pistol/HandShotGun",
+  "/Lotus/Weapons/Tenno/Pistol/HeavyPistol",
+  "/Lotus/Weapons/Tenno/Pistol/Pistol",
+  "/Lotus/Weapons/Tenno/Pistol/RevolverPistol",
+  "/Lotus/Weapons/Tenno/Pistols/ConclaveLeverPistol/ConclaveLeverPistol",
+  "/Lotus/Weapons/Tenno/Rifle/BoltoRifle",
+  "/Lotus/Weapons/Tenno/Rifle/BurstRifle",
+  "/Lotus/Weapons/Tenno/Rifle/HeavyRifle",
+  "/Lotus/Weapons/Tenno/Rifle/Rifle",
+  "/Lotus/Weapons/Tenno/Rifle/SemiAutoRifle",
+  "/Lotus/Weapons/Tenno/Rifle/TennoAR",
+  "/Lotus/Weapons/Tenno/Shotgun/FullAutoShotgun",
+  "/Lotus/Weapons/Tenno/Shotgun/Shotgun",
+  "/Lotus/Weapons/Tenno/ThrowingWeapons/Kunai",
+  "/Lotus/Weapons/Tenno/ThrowingWeapons/StalkerKunai",
+  "/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
+  "/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
+  "/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
+  "/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
+  "/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
+  "/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
+  "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
+]
diff --git a/static/webui/index.html b/static/webui/index.html
index 65a9d832..1d9f3550 100644
--- a/static/webui/index.html
+++ b/static/webui/index.html
@@ -401,6 +401,22 @@
                         
                     
                 
+                
                 
                     
                     
@@ -411,6 +427,7 @@
                             
                             
                             
+                            
                         
                         
                             
@@ -419,6 +436,7 @@
                             
                             
                             
+                            
                         
                      
                 
@@ -733,6 +751,7 @@
     
     
     
+    
     
     
     
diff --git a/static/webui/script.js b/static/webui/script.js
index e8dce874..ebb32177 100644
--- a/static/webui/script.js
+++ b/static/webui/script.js
@@ -183,6 +183,16 @@ const webUiModularWeapons = [
     "/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
 ];
 
+const permanentEvolutionWeapons = new Set([
+    "/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
+    "/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
+    "/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
+    "/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
+    "/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
+    "/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
+    "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
+]);
+
 let uniqueLevelCaps = {};
 function fetchItemList() {
     window.itemListPromise = new Promise(resolve => {
@@ -563,6 +573,82 @@ function updateInventory() {
                 });
             });
 
+            document.getElementById("EvolutionProgress-list").innerHTML = "";
+            data.EvolutionProgress.forEach(item => {
+                const datalist = document.getElementById("datalist-EvolutionProgress");
+                const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
+                if (optionToRemove) {
+                    datalist.removeChild(optionToRemove);
+                }
+                const tr = document.createElement("tr");
+                tr.setAttribute("data-item-type", item.ItemType);
+                {
+                    const td = document.createElement("td");
+                    td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
+                    if (item.Rank != null) {
+                        td.textContent += " | " + loc("code_rank") + ": [" + item.Rank + "/5]";
+                    }
+                    tr.appendChild(td);
+                }
+                {
+                    const td = document.createElement("td");
+                    td.classList = "text-end text-nowrap";
+                    if (item.Rank < 5) {
+                        const a = document.createElement("a");
+                        a.href = "#";
+                        a.onclick = function (event) {
+                            event.preventDefault();
+                            setEvolutionProgress([{ ItemType: item.ItemType, Rank: 5 }]);
+                        };
+                        a.title = loc("code_maxRank");
+                        a.innerHTML = ``;
+
+                        td.appendChild(a);
+                    }
+                    if ((permanentEvolutionWeapons.has(item.ItemType) && item.Rank > 0) || item.Rank > 1) {
+                        const a = document.createElement("a");
+                        a.href = "#";
+                        a.onclick = function (event) {
+                            event.preventDefault();
+                            setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank - 1 }]);
+                        };
+                        a.title = loc("code_rankDown");
+                        a.innerHTML = ``;
+
+                        td.appendChild(a);
+                    }
+                    if (item.Rank < 5) {
+                        const a = document.createElement("a");
+                        a.href = "#";
+                        a.onclick = function (event) {
+                            event.preventDefault();
+                            setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank + 1 }]);
+                        };
+                        a.title = loc("code_rankUp");
+                        a.innerHTML = ``;
+
+                        td.appendChild(a);
+                    }
+
+                    tr.appendChild(td);
+                }
+
+                document.getElementById("EvolutionProgress-list").appendChild(tr);
+            });
+
+            const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
+            const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
+            const giveAllQEvolutionProgress = document.querySelector(
+                'button[onclick*="addMissingEvolutionProgress()"]'
+            );
+
+            if (datalistEvolutionProgress.length === 0) {
+                formEvolutionProgress.classList.add("disabled");
+                formEvolutionProgress.querySelector("input").disabled = true;
+                formEvolutionProgress.querySelector("button").disabled = true;
+                giveAllQEvolutionProgress.disabled = true;
+            }
+
             // Populate quests route
             document.getElementById("QuestKeys-list").innerHTML = "";
             data.QuestKeys.forEach(item => {
@@ -674,18 +760,18 @@ function updateInventory() {
             });
 
             const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option");
-            const form = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
+            const formQuestKeys = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
             const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]");
 
             if (datalistQuestKeys.length === 0) {
-                form.classList.add("disabled");
-                form.querySelector("input").disabled = true;
-                form.querySelector("button").disabled = true;
+                formQuestKeys.classList.add("disabled");
+                formQuestKeys.querySelector("input").disabled = true;
+                formQuestKeys.querySelector("button").disabled = true;
                 giveAllQuestButton.disabled = true;
             } else {
-                form.classList.remove("disabled");
-                form.querySelector("input").disabled = false;
-                form.querySelector("button").disabled = false;
+                formQuestKeys.classList.remove("disabled");
+                formQuestKeys.querySelector("input").disabled = false;
+                formQuestKeys.querySelector("button").disabled = false;
                 giveAllQuestButton.disabled = false;
             }
 
@@ -1080,6 +1166,16 @@ function doAcquireModularEquipment(category, WeaponType) {
     }
 }
 
+function doAcquireEvolution() {
+    const uniqueName = getKey(document.getElementById("acquire-type-EvolutionProgress"));
+    if (!uniqueName) {
+        $("#acquire-type-EvolutionProgress").addClass("is-invalid").focus();
+        return;
+    }
+
+    setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
+}
+
 $("input[list]").on("input", function () {
     $(this).removeClass("is-invalid");
 });
@@ -1117,6 +1213,40 @@ function addMissingEquipment(categories) {
     }
 }
 
+function addMissingEvolutionProgress() {
+    const requests = [];
+    document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
+        const uniqueName = elm.getAttribute("data-key");
+        requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
+    });
+    if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
+        setEvolutionProgress(requests);
+    }
+}
+
+function maxRankAllEvolutions() {
+    const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
+
+    req.done(data => {
+        const requests = [];
+
+        data.EvolutionProgress.forEach(item => {
+            if (item.Rank < 5) {
+                requests.push({
+                    ItemType: item.ItemType,
+                    Rank: 5
+                });
+            }
+        });
+
+        if (Object.keys(requests).length > 0) {
+            return setEvolutionProgress(requests);
+        }
+
+        toast(loc("code_noEquipmentToRankUp"));
+    });
+}
+
 function maxRankAllEquipment(categories) {
     const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
 
@@ -1304,6 +1434,19 @@ function maturePet(oid, revert) {
     });
 }
 
+function setEvolutionProgress(requests) {
+    revalidateAuthz(() => {
+        const req = $.post({
+            url: "/custom/setEvolutionProgress?" + window.authz,
+            contentType: "application/json",
+            data: JSON.stringify(requests)
+        });
+        req.done(() => {
+            updateInventory();
+        });
+    });
+}
+
 function doAcquireMiscItems() {
     const uniqueName = getKey(document.getElementById("miscitem-type"));
     if (!uniqueName) {
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js
index 5dac7438..b0cbc3e2 100644
--- a/static/webui/translations/de.js
+++ b/static/webui/translations/de.js
@@ -34,6 +34,8 @@ dict = {
     code_rerollsNumber: `Anzahl der Umrollversuche`,
     code_viewStats: `Statistiken anzeigen`,
     code_rank: `Rang`,
+    code_rankUp: `[UNTRANSLATED] Rank up`,
+    code_rankDown: `[UNTRANSLATED] Rank down`,
     code_count: `Anzahl`,
     code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
     code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
@@ -84,18 +86,21 @@ dict = {
     inventory_hoverboards: `K-Drives`,
     inventory_moaPets: `Moa`,
     inventory_kubrowPets: `Bestien`,
+    inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
     inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
     inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
     inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
     inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
     inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
     inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
+    inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
     inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
     inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
     inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
     inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`,
     inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
     inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
+    inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
 
     quests_list: `Quests`,
     quests_completeAll: `Alle Quests abschließen`,
diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js
index 23b8133a..3797c6d0 100644
--- a/static/webui/translations/en.js
+++ b/static/webui/translations/en.js
@@ -33,6 +33,8 @@ dict = {
     code_rerollsNumber: `Number of rerolls`,
     code_viewStats: `View Stats`,
     code_rank: `Rank`,
+    code_rankUp: `Rank up`,
+    code_rankDown: `Rank down`,
     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.`,
@@ -83,18 +85,21 @@ dict = {
     inventory_hoverboards: `K-Drives`,
     inventory_moaPets: `Moa`,
     inventory_kubrowPets: `Beasts`,
+    inventory_evolutionProgress: `Incarnon Evolution Progress`,
     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_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
     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`,
+    inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
 
     quests_list: `Quests`,
     quests_completeAll: `Complete All Quests`,
diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js
index c52b68e4..7f19c5e3 100644
--- a/static/webui/translations/es.js
+++ b/static/webui/translations/es.js
@@ -34,6 +34,8 @@ dict = {
     code_rerollsNumber: `Cantidad de reintentos`,
     code_viewStats: `Ver estadísticas`,
     code_rank: `Rango`,
+    code_rankUp: `[UNTRANSLATED] Rank up`,
+    code_rankDown: `[UNTRANSLATED] Rank down`,
     code_count: `Cantidad`,
     code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
     code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`,
@@ -84,18 +86,21 @@ dict = {
     inventory_hoverboards: `K-Drives`,
     inventory_moaPets: `Moa`,
     inventory_kubrowPets: `Bestias`,
+    inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
     inventory_bulkAddSuits: `Agregar Warframes faltantes`,
     inventory_bulkAddWeapons: `Agregar armas faltantes`,
     inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
     inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
     inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
     inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
+    inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
     inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
     inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
     inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
     inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`,
     inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
     inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
+    inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
 
     quests_list: `Misiones`,
     quests_completeAll: `Completar todas las misiones`,
diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js
index 682086aa..31e3cfad 100644
--- a/static/webui/translations/fr.js
+++ b/static/webui/translations/fr.js
@@ -34,6 +34,8 @@ dict = {
     code_rerollsNumber: `Nombre de rerolls`,
     code_viewStats: `Voir les stats`,
     code_rank: `Rang`,
+    code_rankUp: `[UNTRANSLATED] Rank up`,
+    code_rankDown: `[UNTRANSLATED] Rank down`,
     code_count: `Quantité`,
     code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
     code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
@@ -84,18 +86,21 @@ dict = {
     inventory_hoverboards: `K-Drives`,
     inventory_moaPets: `Moa`,
     inventory_kubrowPets: `Bêtes`,
+    inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
     inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
     inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
     inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
     inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
     inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
     inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
+    inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
     inventory_bulkRankUpSuits: `Toutes les Warframes rang max`,
     inventory_bulkRankUpWeapons: `Toutes les armes rang max`,
     inventory_bulkRankUpSpaceSuits: `Tous les Archwings rang max`,
     inventory_bulkRankUpSpaceWeapons: `Toutes les armes d'Archwing rang max`,
     inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`,
     inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`,
+    inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
 
     quests_list: `Quêtes`,
     quests_completeAll: `Compléter toutes les quêtes`,
diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js
index 959678f3..72b38741 100644
--- a/static/webui/translations/ru.js
+++ b/static/webui/translations/ru.js
@@ -34,6 +34,8 @@ dict = {
     code_rerollsNumber: `Количество циклов`,
     code_viewStats: `Просмотр характеристики`,
     code_rank: `Ранг`,
+    code_rankUp: `Повысить Ранг`,
+    code_rankDown: `Понизить Ранг`,
     code_count: `Количество`,
     code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`,
     code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
@@ -84,18 +86,21 @@ dict = {
     inventory_hoverboards: `К-Драйвы`,
     inventory_moaPets: `МОА`,
     inventory_kubrowPets: `Звери`,
+    inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`,
     inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`,
     inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
     inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`,
     inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`,
     inventory_bulkAddSentinels: `Добавить отсутствующих стражей`,
     inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`,
+    inventory_bulkAddEvolutionProgress: `Добавить отсуствующий прогресс эволюции Инкарнонов`,
     inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`,
     inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`,
     inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`,
     inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`,
     inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
     inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
+    inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`,
 
     quests_list: `Квесты`,
     quests_completeAll: `Завершить все квесты`,
diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js
index 3f0e01f5..dd2752a6 100644
--- a/static/webui/translations/zh.js
+++ b/static/webui/translations/zh.js
@@ -34,6 +34,8 @@ dict = {
     code_rerollsNumber: `洗卡次数`,
     code_viewStats: `查看属性`,
     code_rank: `等级`,
+    code_rankUp: `[UNTRANSLATED] Rank up`,
+    code_rankDown: `[UNTRANSLATED] Rank down`,
     code_count: `数量`,
     code_focusAllUnlocked: `所有专精学派均已解锁。`,
     code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`,
@@ -84,18 +86,21 @@ dict = {
     inventory_hoverboards: `K式悬浮板`,
     inventory_moaPets: `恐鸟`,
     inventory_kubrowPets: `[UNTRANSLATED] Beasts`,
+    inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
     inventory_bulkAddSuits: `添加缺失战甲`,
     inventory_bulkAddWeapons: `添加缺失武器`,
     inventory_bulkAddSpaceSuits: `添加缺失Archwing`,
     inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`,
     inventory_bulkAddSentinels: `添加缺失守护`,
     inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
+    inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
     inventory_bulkRankUpSuits: `所有战甲升满级`,
     inventory_bulkRankUpWeapons: `所有武器升满级`,
     inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`,
     inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`,
     inventory_bulkRankUpSentinels: `所有守护升满级`,
     inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
+    inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
 
     quests_list: `任务`,
     quests_completeAll: `完成所有任务`,