diff --git a/TODO.md b/TODO.md
index 1251d005..615892b5 100644
--- a/TODO.md
+++ b/TODO.md
@@ -102,6 +102,21 @@ Per-item detail lives in the dated work log below. **Since 2026-07 versions are
---
+### ★ In-game menu lists speak German: raw ids/enums/literals now localized (#672, 2026-08-02, branch fix/ingame-menu-untranslated-lists)
+On a German client several selection lists still showed English — not missing translations
+(en/de locale parity was already enforced) but strings that never reached the Localizer. Fixed:
+the Tech tab's category sidebar (raw `blueprints.json` categories like `ShipExpansion` → new
+`ui.tech.cat_*` keys), the Map tab's body kind + status (raw `NetBody.Kind`/`Status` enum names →
+`ui.map.kind_*`/`ui.map.status_*`), the mission-creation objective-type cycler (`Mine`/`Collect`/
+`Deliver` → `ui.missions.objtype_*`, wire value unchanged), story-log category tags (`[vega]` →
+`lore.cat.*` in the story pack's own locale files), the crafting "Max" button, the settings
+quality preset (`Potato`… → `ui.settings.preset.*`), the arcade "no record yet" line, and the two
+server-baked POI names (`Ruin A`/`Guardian Core` → per-session-locale `poi.ruin`/`poi.guardian_core`,
+same pattern as `poi.treasure`). New `CraftingTechShipUI.IdLabel(prefix, id)` resolves
+prefix+lowercased id with a raw-id fallback so future data/enum values degrade gracefully. Also
+fixed the dead `Desc()` guard (`s == key` never matches `[key]` misses → now `Localizer.Has`) and
+`VendorTradeUI.ItemName` now parses composite item keys (dye/glow/shape) like the crafting menu.
+
### ★ In-game menu lists show a scrollbar (#664, 2026-08-02, branch feat/ui-inline-scrollbars)
The ship-computer menu (all 12 tabs), the Codex and the vendor dialog scrolled only via wheel/drag —
nothing showed THAT a list continues or WHERE you are. New `UiKit.AddInlineScrollbar(scroll, width)`
diff --git a/client/Assets/BlocksBeyondTheStars/Scripts/ArcadeUI.cs b/client/Assets/BlocksBeyondTheStars/Scripts/ArcadeUI.cs
index 6f5fb6b1..38c873da 100644
--- a/client/Assets/BlocksBeyondTheStars/Scripts/ArcadeUI.cs
+++ b/client/Assets/BlocksBeyondTheStars/Scripts/ArcadeUI.cs
@@ -116,7 +116,7 @@ private void RebuildRail()
// Two-line entry: title on top, personal best on its own line below — so long (German) titles
// and the highscore both fit the 270px frame instead of being crammed onto one shrunk line.
string title = (string.IsNullOrEmpty(e.icon) ? "" : e.icon + " ") + e.Title(Game.German);
- string hs = best > 0 ? "★ " + best : (Game.German ? "noch kein Rekord" : "no record yet");
+ string hs = best > 0 ? "★ " + best : L("ui.arcade.no_record");
var btn = UiKit.AddButton(_rail, 0, y, 270, 64, string.Empty, () => PlayGame(key));
UiKit.AddText(btn.transform, 18, 7, 244, 30, title, 17, UiKit.TextCol, TextAnchor.LowerLeft, FontStyle.Bold);
UiKit.AddText(btn.transform, 18, 37, 244, 20, hs, 13,
diff --git a/client/Assets/BlocksBeyondTheStars/Scripts/CraftingTechShipUI.cs b/client/Assets/BlocksBeyondTheStars/Scripts/CraftingTechShipUI.cs
index d5388703..e3769961 100644
--- a/client/Assets/BlocksBeyondTheStars/Scripts/CraftingTechShipUI.cs
+++ b/client/Assets/BlocksBeyondTheStars/Scripts/CraftingTechShipUI.cs
@@ -625,7 +625,7 @@ private NetStarSystem SelectedSystem()
case Mode.Tech:
foreach (var c in Game.Content.Blueprints.Values.Select(b => b.Category).Where(c => !string.IsNullOrEmpty(c)).Distinct())
{
- list.Add((c, c, "cat_tech"));
+ list.Add((c, IdLabel("ui.tech.cat_", c), "cat_tech"));
}
break;
@@ -1240,8 +1240,8 @@ private float BuildMapList()
{
bool here = b.Id == map.ActiveLocationId;
bool isStation = b.Kind == "SpaceStation";
- string kindLabel = isStation ? L("ui.map.kind_station") : b.Kind;
- string status = here ? L("ui.map.here") : $"{kindLabel} {b.Status}";
+ string kindLabel = isStation ? L("ui.map.kind_station") : IdLabel("ui.map.kind_", b.Kind);
+ string status = here ? L("ui.map.here") : $"{kindLabel} {IdLabel("ui.map.status_", b.Status)}";
// A space station shows whose it is: yours, another player's, or none (procedural/NPC).
if (isStation && !string.IsNullOrEmpty(b.OwnerName))
@@ -1362,13 +1362,14 @@ private float BuildMissionForm()
{
int idx = i;
var o = _pmObjectives[i];
- UiKit.AddText(c, 12, y, 600, 32, $"{o.Type} {o.Required}× {ItemName(o.Target)}", 18, UiKit.TextCol, TextAnchor.MiddleLeft);
+ UiKit.AddText(c, 12, y, 600, 32, $"{IdLabel("ui.missions.objtype_", o.Type)} {o.Required}× {ItemName(o.Target)}", 18, UiKit.TextCol, TextAnchor.MiddleLeft);
UiKit.AddButton(c, 700, y, 60, 32, "✕", () => { _pmObjectives.RemoveAt(idx); RebuildList(); });
y += 38f;
}
// Builder row: type / target / count / add.
- UiKit.AddButton(c, 0, y, 150, 38, PmTypes[_pmType], () => { _pmType = (_pmType + 1) % PmTypes.Length; RebuildList(); });
+ // The cycler shows the localized label; PmTypes stays the wire value (NetMissionObjective.Type).
+ UiKit.AddButton(c, 0, y, 150, 38, IdLabel("ui.missions.objtype_", PmTypes[_pmType]), () => { _pmType = (_pmType + 1) % PmTypes.Length; RebuildList(); });
UiKit.AddButton(c, 158, y, 210, 38, ItemName(PmTargets[_pmTarget]), () => { _pmTarget = (_pmTarget + 1) % PmTargets.Length; RebuildList(); });
UiKit.AddButton(c, 376, y, 44, 38, "−", () => { _pmCount = Mathf.Max(1, _pmCount - 1); RebuildList(); });
UiKit.AddText(c, 422, y, 54, 38, _pmCount.ToString(), 20, UiKit.TextCol, TextAnchor.MiddleCenter, FontStyle.Bold);
@@ -2156,7 +2157,7 @@ private float BuildAchievementList()
}
UiKit.AddText(_listContent, 8, y, 760, 36,
- (L("ui.achv.summary") ?? "{done}/{total}").Replace("{done}", done.ToString()).Replace("{total}", all.Length.ToString()),
+ L("ui.achv.summary").Replace("{done}", done.ToString()).Replace("{total}", all.Length.ToString()),
24, UiKit.Cyan, TextAnchor.MiddleLeft, FontStyle.Bold);
y += 46f;
@@ -2199,7 +2200,7 @@ private float AchievementRow(float y, BlocksBeyondTheStars.Networking.Messages.N
string tally = a.Earned
? L("ui.achv.done")
- : (L("ui.achv.progress") ?? "{done}/{total}")
+ : L("ui.achv.progress")
.Replace("{done}", a.Progress.ToString())
.Replace("{total}", a.Target.ToString());
UiKit.AddText(_listContent, RowW - 140f, y, 140f, 28, tally, 18,
@@ -2258,7 +2259,7 @@ private float BuildStoryList()
{
foreach (var (cat, key) in Game.StoryLogFragments)
{
- y = StoryEntry(y, "[" + cat + "] " + L(key));
+ y = StoryEntry(y, "[" + IdLabel("lore.cat.", cat) + "] " + L(key));
}
}
@@ -2495,7 +2496,7 @@ private float DetailCrafting()
UiKit.AddButton(_detail, 8, y, 50, 56, "-", () => { _craftCount = Mathf.Max(1, _craftCount - 1); RebuildDetail(); });
UiKit.AddText(_detail, 62, y, 92, 56, _craftCount.ToString(), 24, UiKit.TextCol, TextAnchor.MiddleCenter, FontStyle.Bold);
UiKit.AddButton(_detail, 158, y, 50, 56, "+", () => { _craftCount = Mathf.Min(maxCraft, _craftCount + 1); RebuildDetail(); });
- UiKit.AddButton(_detail, 214, y, 74, 56, "Max", () => { _craftCount = maxCraft; RebuildDetail(); });
+ UiKit.AddButton(_detail, 214, y, 74, 56, L("ui.craft.max"), () => { _craftCount = maxCraft; RebuildDetail(); });
y += 66f;
int n = _craftCount;
@@ -2910,7 +2911,7 @@ private float DetailMap()
bool isStation = body.Kind == "SpaceStation";
UiKit.AddText(_detail, 8, y, 620, 40, body.Name, 30, UiKit.TextCol, TextAnchor.UpperLeft, FontStyle.Bold);
y += 48f;
- UiKit.AddText(_detail, 8, y, 620, 28, $"{L("ui.map.kind")}: {(isStation ? L("ui.map.kind_station") : body.Kind)}", 20, UiKit.CyanDim, TextAnchor.UpperLeft);
+ UiKit.AddText(_detail, 8, y, 620, 28, $"{L("ui.map.kind")}: {(isStation ? L("ui.map.kind_station") : IdLabel("ui.map.kind_", body.Kind))}", 20, UiKit.CyanDim, TextAnchor.UpperLeft);
y += 32f;
if (!string.IsNullOrEmpty(body.PlanetType))
{
@@ -2919,7 +2920,7 @@ private float DetailMap()
}
bool here = body.Id == map.ActiveLocationId;
- UiKit.AddText(_detail, 8, y, 620, 28, here ? L("ui.map.here") : body.Status, 20, here ? UiKit.Cyan : UiKit.CyanDim, TextAnchor.UpperLeft);
+ UiKit.AddText(_detail, 8, y, 620, 28, here ? L("ui.map.here") : IdLabel("ui.map.status_", body.Status), 20, here ? UiKit.Cyan : UiKit.CyanDim, TextAnchor.UpperLeft);
y += 40f;
// Colour-mark this body. Asked for by a player who wanted to mark planets in space in different
@@ -3443,6 +3444,15 @@ private static void ClearChildren(Transform t)
private string L(string key) => Game?.Localizer?.Get(key) ?? key;
+ /// Localized label for a raw data/enum identifier (blueprint category, body kind/status,
+ /// objective type, story-fragment category): resolves + the lower-cased id
+ /// and falls back to the raw id for values without a key, so new data/enum members degrade to
+ /// today's behaviour instead of showing a bracketed key.
+ private string IdLabel(string prefix, string id)
+ => string.IsNullOrEmpty(id) || Game?.Localizer?.Has(prefix + id.ToLowerInvariant()) != true
+ ? id
+ : L(prefix + id.ToLowerInvariant());
+
// --- Coloured planet marks in the star map ---------------------------------------------------------
// A player wanted to mark planets in space, each in its own colour — several at once, unlike the single
// surface waypoint. Stored locally in ClientSettings (never sent to the server) and grouped by world, so
@@ -3503,8 +3513,9 @@ private string ItemName(string item)
}
private string Desc(string key)
{
- string s = L(key);
- return s == key ? string.Empty : s;
+ // Localizer.Get returns "[key]" (never the bare key) on a miss, so comparing against the key
+ // can't detect one — ask Has() instead, like WikiUI does, and show nothing for absent texts.
+ return Game?.Localizer?.Has(key) == true ? L(key) : string.Empty;
}
}
}
diff --git a/client/Assets/BlocksBeyondTheStars/Scripts/UiSettings.cs b/client/Assets/BlocksBeyondTheStars/Scripts/UiSettings.cs
index 7122dec2..a2d8b9a9 100644
--- a/client/Assets/BlocksBeyondTheStars/Scripts/UiSettings.cs
+++ b/client/Assets/BlocksBeyondTheStars/Scripts/UiSettings.cs
@@ -70,7 +70,7 @@ private void Rebuild()
Head(ref y, L("ui.settings.graphics"));
// Graphics rows apply immediately (like the window-mode row always did) so changes made from the
// in-game pause menu are visible right away instead of only when the settings screen closes.
- Cycle(ref y, L("ui.settings.preset"), S.Preset.ToString(), () => { S.Preset = (QualityPreset)(((int)S.Preset + 1) % 4); S.Apply(); ApplyLiveWorld(); Rebuild(); });
+ Cycle(ref y, L("ui.settings.preset"), L("ui.settings.preset." + S.Preset.ToString().ToLowerInvariant()), () => { S.Preset = (QualityPreset)(((int)S.Preset + 1) % 4); S.Apply(); ApplyLiveWorld(); Rebuild(); });
// Window mode cycles Windowed → Borderless → Exclusive and applies immediately so the player sees
// the window change without leaving the menu (resolution/mode changes are pushed via Apply()).
Cycle(ref y, L("ui.settings.window_mode"), L(WindowModeKey(S.Window)),
diff --git a/client/Assets/BlocksBeyondTheStars/Scripts/VendorTradeUI.cs b/client/Assets/BlocksBeyondTheStars/Scripts/VendorTradeUI.cs
index 73c7774b..0a80d227 100644
--- a/client/Assets/BlocksBeyondTheStars/Scripts/VendorTradeUI.cs
+++ b/client/Assets/BlocksBeyondTheStars/Scripts/VendorTradeUI.cs
@@ -3,6 +3,8 @@
// This file is part of Blocks Beyond the Stars. See LICENSE for the full AGPL-3.0 text.
using System.Linq;
using BlocksBeyondTheStars.Networking.Messages;
+using BlocksBeyondTheStars.Shared.State;
+using BlocksBeyondTheStars.Shared.World;
using UnityEngine;
using UnityEngine.UI;
@@ -91,7 +93,31 @@ private void OnInventory(InventoryUpdate m)
private string L(string key) => Game?.Localizer?.Get(key) ?? key;
- private string ItemName(string item) => L($"item.{item}.name");
+ /// Display name for a (possibly composite) item key: base name via the base key, plus
+ /// dyed/glowing and shape suffixes — so a market recipe over a modified item never renders a
+ /// bracketed key like [item.stone#t3f6fb0.name]. Shape names follow the
+ /// ui.shape.<BlockShape> key family (indices match ).
+ private string ItemName(string item)
+ {
+ var (baseKey, tint, glow) = ItemKey.Parse(item);
+ string name = L($"item.{baseKey}.name");
+ if (glow != 0)
+ {
+ name += " · " + L("ui.color.glowing");
+ }
+ else if (tint != 0)
+ {
+ name += " · " + L("ui.color.dyed");
+ }
+
+ int shape = ItemKey.Shape(item);
+ if (shape != 0)
+ {
+ name += " · " + L("ui.shape." + ((BlockShape)shape).ToString().ToLowerInvariant());
+ }
+
+ return name;
+ }
private void Build()
{
diff --git a/data/locales/de.json b/data/locales/de.json
index fa6baab4..9736a444 100644
--- a/data/locales/de.json
+++ b/data/locales/de.json
@@ -604,6 +604,7 @@
"ui.arcade.broken_title": "Spiele konnten nicht geladen werden",
"ui.arcade.broken_body": "Die Minispiel-Inhalte fehlen oder sind beschädigt, deshalb lassen sich deine Datenfragmente nicht öffnen. Starte das Spiel neu oder installiere es neu.",
"ui.arcade.downloaded": "Datenfragment geborgen!",
+ "ui.arcade.no_record": "noch kein Rekord",
"ui.browser.unavailable_title": "Browser-Komponente nicht installiert",
"ui.browser.unavailable_body": "Der eingebettete Browser (UnityWebBrowser) ist in diesem Build nicht vorhanden. Siehe docs/developer/MINIGAMES_AND_WIKI.md zum Aktivieren.",
"ui.browser.loading": "Lädt…",
@@ -659,6 +660,14 @@
"ui.map.pick_destination": "Wähle links ein Reiseziel.",
"ui.map.locked_hint": "Hier noch nie gelandet — flieg hin und lande zuerst manuell (oder aktiviere Instant Travel).",
"ui.map.kind_station": "Raumstation",
+ "ui.map.kind_planet": "Planet",
+ "ui.map.kind_moon": "Mond",
+ "ui.map.kind_asteroidfield": "Asteroidenfeld",
+ "ui.map.kind_wreck": "Wrack",
+ "ui.map.status_notgenerated": "Unerforscht",
+ "ui.map.status_generated": "Kartiert",
+ "ui.map.status_discovered": "Entdeckt",
+ "ui.map.status_visited": "Besucht",
"ui.map.owner": "Besitzer",
"ui.map.your_station": "Deine Station",
"ui.map.station_of": "Station von",
@@ -690,6 +699,8 @@
"npc.hint.wreck": "Sag mal — ich habe da draußen mal ein abgestürztes Schiff gesehen, etwa {0} m Richtung {1}. Ich habe es dir auf der Karte markiert.",
"npc.hint.treasure": "Unter uns: Etwa {0} m Richtung {1} liegt ein verstecktes Lager. Ich habe es dir auf der Karte markiert — erzähl es nicht jedem.",
"poi.treasure": "Verstecktes Lager",
+ "poi.ruin": "Ruine {0}",
+ "poi.guardian_core": "Wächterkern",
"dir.n": "Norden",
"dir.ne": "Nordosten",
"dir.e": "Osten",
@@ -710,8 +721,12 @@
"ui.missions.post": "Mission posten",
"ui.missions.need_fields": "Titel + mindestens ein Ziel nötig.",
"ui.missions.giver": "Auftrag von",
+ "ui.missions.objtype_mine": "Abbauen",
+ "ui.missions.objtype_collect": "Sammeln",
+ "ui.missions.objtype_deliver": "Liefern",
"ui.crafting.title": "Herstellung",
"ui.craft.craftable_now": "Jetzt baubar",
+ "ui.craft.max": "Max",
"ui.craft.cat_all": "Alle",
"ui.craft.cat_tools": "Werkzeuge",
"ui.craft.cat_weapons": "Waffen",
@@ -805,6 +820,15 @@
"ui.tech.materials_missing": "Material fehlt",
"ui.tech.unlockable": "Freischaltbar",
"ui.tech.tier": "Stufe",
+ "ui.tech.cat_production": "Produktion",
+ "ui.tech.cat_ship": "Schiff",
+ "ui.tech.cat_shipdefense": "Schiffsverteidigung",
+ "ui.tech.cat_shipexpansion": "Schiffsausbau",
+ "ui.tech.cat_shipweapon": "Schiffswaffen",
+ "ui.tech.cat_station": "Station",
+ "ui.tech.cat_suit": "Anzug",
+ "ui.tech.cat_tools": "Werkzeuge",
+ "ui.tech.cat_weapon": "Waffen",
"ui.toggle.on": "An",
"ui.toggle.off": "Aus",
"ui.crafting.missing": "Fehlende Materialien",
@@ -1378,6 +1402,10 @@
"ui.key.request_trade": "Handel anfragen",
"ui.key.request_dock": "Andocken anfragen",
"ui.settings.preset": "Qualitätsvorgabe",
+ "ui.settings.preset.potato": "Kartoffel",
+ "ui.settings.preset.low": "Niedrig",
+ "ui.settings.preset.medium": "Mittel",
+ "ui.settings.preset.high": "Hoch",
"ui.settings.window_mode": "Fenstermodus",
"ui.settings.window_mode.windowed": "Fenster",
"ui.settings.window_mode.borderless": "Randloses Vollbild",
diff --git a/data/locales/en.json b/data/locales/en.json
index 716726c2..3c42c54d 100644
--- a/data/locales/en.json
+++ b/data/locales/en.json
@@ -603,6 +603,7 @@
"ui.arcade.broken_title": "Games couldn't be loaded",
"ui.arcade.broken_body": "The minigame content is missing or damaged, so your data fragments can't be opened. Try restarting the game or reinstalling.",
"ui.arcade.downloaded": "Data fragment recovered!",
+ "ui.arcade.no_record": "no record yet",
"ui.browser.unavailable_title": "Browser component not installed",
"ui.browser.unavailable_body": "The embedded browser (UnityWebBrowser) is not present in this build. See docs/developer/MINIGAMES_AND_WIKI.md to enable it.",
"ui.browser.loading": "Loading…",
@@ -658,6 +659,14 @@
"ui.map.pick_destination": "Pick a destination on the left.",
"ui.map.locked_hint": "Never landed here — fly there and land manually first (or enable Instant Travel).",
"ui.map.kind_station": "Space Station",
+ "ui.map.kind_planet": "Planet",
+ "ui.map.kind_moon": "Moon",
+ "ui.map.kind_asteroidfield": "Asteroid field",
+ "ui.map.kind_wreck": "Wreck",
+ "ui.map.status_notgenerated": "Uncharted",
+ "ui.map.status_generated": "Charted",
+ "ui.map.status_discovered": "Discovered",
+ "ui.map.status_visited": "Visited",
"ui.map.owner": "Owner",
"ui.map.your_station": "Your station",
"ui.map.station_of": "Station of",
@@ -689,6 +698,8 @@
"npc.hint.wreck": "Say — I once saw a crashed ship out there, about {0} m to the {1}. I've marked it on your map.",
"npc.hint.treasure": "Between us: there's a hidden cache about {0} m to the {1}. I've marked it on your map — don't tell everyone.",
"poi.treasure": "Hidden cache",
+ "poi.ruin": "Ruin {0}",
+ "poi.guardian_core": "Guardian Core",
"dir.n": "north",
"dir.ne": "northeast",
"dir.e": "east",
@@ -709,8 +720,12 @@
"ui.missions.post": "Post mission",
"ui.missions.need_fields": "Add a title and at least one objective.",
"ui.missions.giver": "Mission from",
+ "ui.missions.objtype_mine": "Mine",
+ "ui.missions.objtype_collect": "Collect",
+ "ui.missions.objtype_deliver": "Deliver",
"ui.crafting.title": "Crafting",
"ui.craft.craftable_now": "Craftable now",
+ "ui.craft.max": "Max",
"ui.craft.cat_all": "All",
"ui.craft.cat_tools": "Tools",
"ui.craft.cat_weapons": "Weapons",
@@ -804,6 +819,15 @@
"ui.tech.materials_missing": "Materials missing",
"ui.tech.unlockable": "Unlockable",
"ui.tech.tier": "Tier",
+ "ui.tech.cat_production": "Production",
+ "ui.tech.cat_ship": "Ship",
+ "ui.tech.cat_shipdefense": "Ship defense",
+ "ui.tech.cat_shipexpansion": "Ship expansion",
+ "ui.tech.cat_shipweapon": "Ship weapons",
+ "ui.tech.cat_station": "Station",
+ "ui.tech.cat_suit": "Suit",
+ "ui.tech.cat_tools": "Tools",
+ "ui.tech.cat_weapon": "Weapons",
"ui.toggle.on": "On",
"ui.toggle.off": "Off",
"ui.crafting.missing": "Missing materials",
@@ -1377,6 +1401,10 @@
"ui.key.request_trade": "Request trade",
"ui.key.request_dock": "Request dock",
"ui.settings.preset": "Quality preset",
+ "ui.settings.preset.potato": "Potato",
+ "ui.settings.preset.low": "Low",
+ "ui.settings.preset.medium": "Medium",
+ "ui.settings.preset.high": "High",
"ui.settings.window_mode": "Window mode",
"ui.settings.window_mode.windowed": "Windowed",
"ui.settings.window_mode.borderless": "Borderless fullscreen",
diff --git a/data/stories/vega_protocol/locales/de.json b/data/stories/vega_protocol/locales/de.json
index 41b0057f..ca16bff9 100644
--- a/data/stories/vega_protocol/locales/de.json
+++ b/data/stories/vega_protocol/locales/de.json
@@ -13,6 +13,12 @@
"story.vega.beat10": "Du bist nicht der Einzige. Jeder andere Raumfahrer ist ein anderer Abdruck, ein anderes verlorenes Leben, erweckt, um den Service weiterzutragen.",
"story.vega.beat11": "Der Wächterkern wurde nie zerstört, nur stillgelegt. Ich spüre, wo er schläft. Weckt ihn alte Technik, beginnt die Jagd von Neuem.",
"story.vega.beat12": "Du bist mehr als eine Kopie der Vergangenheit. Was aus diesem Erbe wird, entscheidest du. Beenden wir das, und bauen wir etwas Neues.",
+ "lore.cat.vega": "VEGA",
+ "lore.cat.sps": "Pionierdienst",
+ "lore.cat.guardian": "Wächter",
+ "lore.cat.network": "Sternennetz",
+ "lore.cat.settler": "Siedler",
+ "lore.cat.netnode": "Netzknoten",
"lore.frag.frag_vega_signature": "[Archiv beschädigt] …Knotenkennung bestätigt… Signatur stimmt mit dieser Einheit überein…",
"lore.frag.frag_sps_outpost7": "[Archiv beschädigt] …Scout and Pioneer Service, Außenposten 7… Routen gesichert… erwarten Kolonistenwelle…",
"lore.frag.frag_guardian_verdict": "Protokoll W: Biom-Integrität über allem. Bedrohung erkannt: zweibeinige Siedler. Maßnahme: Beseitigung.",
diff --git a/data/stories/vega_protocol/locales/en.json b/data/stories/vega_protocol/locales/en.json
index c382d0c1..1ba3a3c7 100644
--- a/data/stories/vega_protocol/locales/en.json
+++ b/data/stories/vega_protocol/locales/en.json
@@ -13,6 +13,12 @@
"story.vega.beat10": "You are not the only one. Every other spacefarer is a different imprint, a different lost life, woken to carry the Service on.",
"story.vega.beat11": "The Guardian core was never destroyed, only stilled. I can feel where it sleeps. If old tech wakes it, the hunt begins again.",
"story.vega.beat12": "You are more than a copy of the past. What this legacy becomes is yours to decide. Let us end this, and build something new.",
+ "lore.cat.vega": "VEGA",
+ "lore.cat.sps": "Pioneer Service",
+ "lore.cat.guardian": "Guardian",
+ "lore.cat.network": "Star network",
+ "lore.cat.settler": "Settlers",
+ "lore.cat.netnode": "Net node",
"lore.frag.frag_vega_signature": "[archive corrupted] ...node identity confirmed... signature matches this unit...",
"lore.frag.frag_sps_outpost7": "[archive corrupted] ...Scout and Pioneer Service, Outpost 7... routes secured... awaiting colonist wave...",
"lore.frag.frag_guardian_verdict": "Protocol G: biome integrity above all. Threat identified: bipedal settlers. Action: removal.",
diff --git a/src/BlocksBeyondTheStars.GameServer/GameServerSettlements.cs b/src/BlocksBeyondTheStars.GameServer/GameServerSettlements.cs
index 9aff2af3..6cc079fe 100644
--- a/src/BlocksBeyondTheStars.GameServer/GameServerSettlements.cs
+++ b/src/BlocksBeyondTheStars.GameServer/GameServerSettlements.cs
@@ -126,7 +126,7 @@ private List BuildPlanetPois(PlayerSession session)
pois.Add(new NetPoi
{
Type = "vault_ruin",
- Name = "Ruin " + (char)('A' + i),
+ Name = string.Format(Localize(session.Locale, "poi.ruin"), (char)('A' + i)),
X = _vaultEntrances[i].X,
Z = _vaultEntrances[i].Z,
});
@@ -136,7 +136,7 @@ private List BuildPlanetPois(PlayerSession session)
if (_worlds.Active.HasCoreChamber)
{
var c = _worlds.Active.CoreChamberCenter;
- pois.Add(new NetPoi { Type = "guardian_core", Name = "Guardian Core", X = c.X, Z = c.Z });
+ pois.Add(new NetPoi { Type = "guardian_core", Name = Localize(session.Locale, "poi.guardian_core"), X = c.X, Z = c.Z });
}
// NPC-hint reveals: the wreck + treasure chests stay OFF the map until a villager shares them