feat(launcher): route all UI strings through t(key) indirection
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.
This commit is contained in:
42
init.lua
42
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)
|
||||
|
||||
Reference in New Issue
Block a user