Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand Down
2 changes: 1 addition & 1 deletion client/Assets/BlocksBeyondTheStars/Scripts/ArcadeUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 24 additions & 13 deletions client/Assets/BlocksBeyondTheStars/Scripts/CraftingTechShipUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))
{
Expand All @@ -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
Expand Down Expand Up @@ -3443,6 +3444,15 @@ private static void ClearChildren(Transform t)

private string L(string key) => Game?.Localizer?.Get(key) ?? key;

/// <summary>Localized label for a raw data/enum identifier (blueprint category, body kind/status,
/// objective type, story-fragment category): resolves <paramref name="prefix"/> + 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.</summary>
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
Expand Down Expand Up @@ -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;
}
}
}
2 changes: 1 addition & 1 deletion client/Assets/BlocksBeyondTheStars/Scripts/UiSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
28 changes: 27 additions & 1 deletion client/Assets/BlocksBeyondTheStars/Scripts/VendorTradeUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
/// <summary>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 <c>[item.stone#t3f6fb0.name]</c>. Shape names follow the
/// <c>ui.shape.&lt;BlockShape&gt;</c> key family (indices match <see cref="BlockShape"/>).</summary>
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()
{
Expand Down
28 changes: 28 additions & 0 deletions data/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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…",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
28 changes: 28 additions & 0 deletions data/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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…",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Loading