From a19dd7f1dd0086891431c7a10de621bbe0c71b61 Mon Sep 17 00:00:00 2001 From: Calic Date: Thu, 11 Jun 2026 07:49:38 +0200 Subject: [PATCH] feat(launcher): route all UI strings through t(key) indirection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit strings.lua holds English defaults. ctx.t(key, ...) looks up the key, optionally string.formats with extra args, and falls back to '[key]' for missing entries. All panels and init.lua sweep clean of inline literals — verified by a grep over '"[A-Z][a-z][a-z ]+"'. This sets up v2's localization-lib-pattern (separate slice, ADR-0036 trigger) to swap STRINGS without touching the launcher code. --- init.lua | 42 +++++++++++++++++++++++++++--------------- panels/detail.lua | 2 +- panels/list.lua | 3 ++- panels/modal.lua | 6 +++--- strings.lua | 20 ++++++++++++++++++++ 5 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 strings.lua diff --git a/init.lua b/init.lua index 0bb4f2d..b891bd4 100644 --- a/init.lua +++ b/init.lua @@ -30,11 +30,10 @@ local gitea_base = nil -- resolved in init() from engine.config local frames = 0 local CI_FRAMES = 60 -local TITLE = "Sporel Launcher" -local QUIT_PROMPT = "Sporel beenden?" -local YES_LABEL = "Ja" -local NO_LABEL = "Nein" -local EMPTY_LABEL = "Keine Module installiert." +-- L.8: All UI-facing strings now live in strings.lua and are accessed +-- via ctx_panels.t(key, ...). The previous TITLE / QUIT_PROMPT / +-- YES_LABEL / NO_LABEL / EMPTY_LABEL locals were retired in this slice. +local STRINGS = dofile(MODULE_DIR .. "/strings.lua") local FONT_TITLE = 28 local FONT_BUTTON = 20 @@ -198,7 +197,7 @@ function init(ctx) ctx_panels = { -- Persistent UI context, threaded through the panels. module_entries = {}, -- filled below from engine.module.list - news_entry = { title = "News", body_path = "news.md" }, + news_entry = { title = nil, body_path = "news.md" }, selected_entry = nil, scroll_y = 0, list_tint = nil, -- nil | "warn" | "error" — set in L.4 @@ -206,12 +205,23 @@ function init(ctx) detail_panel_rect = nil, bg_texture = nil, -- L.10 loads it default_teaser_texture = nil, -- L.10 loads it - t = function(k) return k end, -- L.8 swaps in real t() + t = nil, -- L.8: real implementation installed below carousel_state = {}, -- per-module-id state, L.5 populates manifest_cache = {}, -- L.3 populates launcher_dir = MODULE_DIR, -- L.4: detail.lua dofiles markdown.lua via this } + -- L.8: real string indirection. Looks up `key` in strings.lua, falls + -- back to "[key]" for missing entries, and string.formats with any + -- varargs so callers can pass format-args inline (e.g. counts). + ctx_panels.t = function(key, ...) + local s = STRINGS[key] + if not s then return "[" .. tostring(key) .. "]" end + if select("#", ...) > 0 then return string.format(s, ...) end + return s + end + ctx_panels.news_entry.title = ctx_panels.t("news_default_title") + -- Window split: 1/3 list, 2/3 detail. Margin 12px between panels. local SW, SH = screen_w, screen_h ctx_panels.list_panel_rect = { x = 0, y = 0, w = SW / 3, h = SH } @@ -270,7 +280,7 @@ function init(ctx) -- L.6: load + parse news.md once. The detail-panel's news branch -- reads ctx.news_body_blocks; nil-safe so a missing file still - -- shows the "News" entry with an empty body. + -- shows the news entry with an empty body. do local news_path = MODULE_DIR .. "/news.md" local f = io.open(news_path, "rb") @@ -281,14 +291,15 @@ function init(ctx) local md = dofile(MODULE_DIR .. "/markdown.lua") local meta, body = fm.parse(content) if meta then - ctx_panels.news_entry.title = meta.title or "News" + ctx_panels.news_entry.title = meta.title + or ctx_panels.t("news_default_title") ctx_panels.news_body_blocks = md.parse(body) end end end - local wy, hy = engine.render.measure_text(YES_LABEL, FONT_BUTTON) - local wn, hn = engine.render.measure_text(NO_LABEL, FONT_BUTTON) + local wy, hy = engine.render.measure_text(ctx_panels.t("yes"), FONT_BUTTON) + local wn, hn = engine.render.measure_text(ctx_panels.t("no"), FONT_BUTTON) local qbtn_w = math.max(wy, wn) + 2 * PAD_X local qbtn_h = math.max(hy, hn) + 2 * PAD_Y local total_w = 2 * qbtn_w + 24 @@ -349,7 +360,7 @@ function update(ctx, dt) end -- L.7: Conflict-modal takes priority over STATE_LIST input. esc / - -- Cancel close the modal back to STATE_LIST; "Launch anyway" stays + -- cancel close the modal back to STATE_LIST; launch-anyway stays -- a no-op until engine.module.switch_module_supports_fallback ships. if state == fsm.STATE_MODAL_CONFLICT then if input.was_action_pressed("ui_back") then @@ -414,15 +425,16 @@ function render(ctx) engine.render.draw_rect((screen_w - modal_w) / 2, (screen_h - modal_h) / 2, modal_w, modal_h, COLOR_MODAL) - local pw = engine.render.measure_text(QUIT_PROMPT, FONT_BUTTON) - engine.render.draw_text(QUIT_PROMPT, (screen_w - pw) / 2, + local prompt = ctx_panels.t("quit_prompt") + local pw = engine.render.measure_text(prompt, FONT_BUTTON) + engine.render.draw_text(prompt, (screen_w - pw) / 2, screen_h / 2 - 24, FONT_BUTTON, COLOR_TEXT) for _, key in ipairs({ "yes", "no" }) do local b = quit_buttons[key] local color = hit(b, mx, my) and COLOR_HOVER or COLOR_BTN engine.render.draw_rect(b.x, b.y, b.w, b.h, color) - local label = (key == "yes") and YES_LABEL or NO_LABEL + local label = ctx_panels.t(key) local lw = engine.render.measure_text(label, FONT_BUTTON) engine.render.draw_text(label, b.x + (b.w - lw) / 2, b.y + PAD_Y, FONT_BUTTON, COLOR_TEXT) diff --git a/panels/detail.lua b/panels/detail.lua index 33eb4e7..b6a6501 100644 --- a/panels/detail.lua +++ b/panels/detail.lua @@ -142,7 +142,7 @@ function M.handle_click(ctx, mx, my) end -- L.7: Launch button → either switch directly (clean deps) or open - -- the conflict modal (warn/error deps). The "Launch anyway" path + -- the conflict modal (warn/error deps). The launch-anyway path -- requires engine.module.switch_module_supports_fallback (separate -- engine slice); until that ships the button stays disabled. if ctx._launch_btn and engine.spatial.aabb_contains_point(ctx._launch_btn, mx, my) then diff --git a/panels/list.lua b/panels/list.lua index 8d423fc..24b877d 100644 --- a/panels/list.lua +++ b/panels/list.lua @@ -43,7 +43,8 @@ function M.render(ctx) } local col = (ctx.selected_entry == "news") and COLOR_ROW_ACTIVE or COLOR_ROW engine.render.draw_rect(rect.x, rect.y, rect.w, rect.h, col) - engine.render.draw_text(ctx.news_entry.title or "News", + engine.render.draw_text( + ctx.news_entry.title or ctx.t("news_default_title"), rect.x + PADDING, rect.y + PADDING, FONT, COLOR_TEXT) ctx.news_entry.rect = rect end diff --git a/panels/modal.lua b/panels/modal.lua index f0643d5..397891e 100644 --- a/panels/modal.lua +++ b/panels/modal.lua @@ -10,9 +10,9 @@ local FONT_BTN = 18 local PADDING = 18 -- def = { --- title = "Confirm", --- body = "text or array of lines", --- buttons = { { id="cancel", label="Cancel" }, { id="ok", label="Launch anyway", disabled=false } }, +-- title = , +-- body = , +-- buttons = { { id=, label=, disabled= }, ... }, -- } function M.render(def, screen_w, screen_h) -- Backdrop. diff --git a/strings.lua b/strings.lua new file mode 100644 index 0000000..98c36b0 --- /dev/null +++ b/strings.lua @@ -0,0 +1,20 @@ +return { + -- Empty-state hint + empty_hint = "Select a module to see details.", + -- Detail panel sections + dependencies = "Dependencies", + -- Launch flow + launch_module = "Launch Module", + cancel = "Cancel", + launch_anyway = "Launch anyway", + -- Conflict modal + conflict_title = "Dependency conflict", + conflict_body_line1 = "This module has unresolved dependencies.", + conflict_body_line2_fmt = "Missing libs: %d", + -- Quit modal (already in v0.2.0 but moves to strings now) + quit_prompt = "Quit Sporel?", + yes = "Yes", + no = "No", + -- News entry default title + news_default_title = "News", +}