mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
translate the launcher: load mod string catalogs before it draws (#794)
the launcher runs before Game:load, so #767's Strings hooks had nothing filling the catalog and a restart could not help - the ordering is the same every launch. reads lang/strings.lua from enabled mods only, data not the entry chunk, sandboxed. plus a plain pixel fallback on the ui faces, or the kana draw as tofu.
This commit is contained in:
@@ -254,6 +254,80 @@ function LauncherMods.list()
|
||||
return result or {}
|
||||
end
|
||||
|
||||
-- ------- pre-boot translation strings
|
||||
--
|
||||
-- The launcher draws before Game:load, so the loader has not run and Strings
|
||||
-- has no catalog. #767/#791 routed the launcher's text through Strings, but
|
||||
-- nothing filled the catalog this early, so a translation mod still could not
|
||||
-- reach the launcher however complete it was -- and no restart helped, because
|
||||
-- the ordering is the same on every launch.
|
||||
--
|
||||
-- This fills it, and deliberately does the smallest thing that can: one
|
||||
-- declarative file per enabled mod, lang/strings.lua, and never the entry
|
||||
-- chunk. That keeps the promise the rest of this module is built on -- no mod
|
||||
-- behaviour runs before the game boots -- because a catalog is data.
|
||||
--
|
||||
-- It is still a mod-authored chunk, so it runs with an empty environment: a
|
||||
-- plain `return { ... }` evaluates fine, while anything reaching for love, io
|
||||
-- or os raises and is skipped rather than being trusted this early.
|
||||
--
|
||||
-- Game:load calls Strings.load(Data) again after the real merge, which
|
||||
-- replaces whatever this installed, so the two never disagree for long.
|
||||
local STRINGS_CATALOG = "lang/strings.lua"
|
||||
|
||||
local function readStringsCatalog(path)
|
||||
local fs = love and love.filesystem
|
||||
if not (fs and fs.read) then return nil end
|
||||
local rel = path .. "/" .. STRINGS_CATALOG
|
||||
local raw = fs.read(rel)
|
||||
if type(raw) ~= "string" or raw == "" then return nil end
|
||||
local chunk = loadstring(raw, "@" .. rel)
|
||||
if not chunk then return nil end
|
||||
-- Lua 5.1/LuaJIT: no _ENV, so setfenv is the sandbox.
|
||||
if setfenv then setfenv(chunk, {}) end
|
||||
local ok, result = pcall(chunk)
|
||||
if not ok or type(result) ~= "table" then return nil end
|
||||
return result
|
||||
end
|
||||
|
||||
-- deriveStrings(rows, byId, read) -> the merged catalog, pure.
|
||||
-- rows is deriveList's output, byId the id -> manifest map, and read(path) a
|
||||
-- reader returning that mod's catalog table (or nil). Split out so the engine
|
||||
-- tier can table-drive the enable/precedence rules with no filesystem.
|
||||
function LauncherMods.deriveStrings(rows, byId, read)
|
||||
local out, any = {}, false
|
||||
for _, row in ipairs(rows or {}) do
|
||||
local manifest = row.enabled and byId and byId[row.id] or nil
|
||||
local catalog = manifest and manifest.path and read(manifest.path)
|
||||
for source, value in pairs(catalog or {}) do
|
||||
-- an empty value means "not translated yet", never "translate to
|
||||
-- blank" -- the same rule the mod's own loader applies
|
||||
if type(source) == "string" and type(value) == "string"
|
||||
and value ~= "" then
|
||||
out[source] = value
|
||||
any = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return any and out or nil
|
||||
end
|
||||
|
||||
-- translationStrings() -> a source -> translation map for the launcher, or nil
|
||||
-- when no enabled mod ships one. Enable-state and ordering are deriveList's,
|
||||
-- so a mod that wins a key here wins it at boot too.
|
||||
function LauncherMods.translationStrings()
|
||||
local ok, merged = pcall(function()
|
||||
local manifests = discover()
|
||||
if #manifests == 0 then return nil end
|
||||
local rows = LauncherMods.deriveList(manifests, SaveData.loadOptions())
|
||||
local byId = {}
|
||||
for _, m in ipairs(manifests) do byId[m.id] = m end
|
||||
return LauncherMods.deriveStrings(rows, byId, readStringsCatalog)
|
||||
end)
|
||||
if not ok then return nil end
|
||||
return merged
|
||||
end
|
||||
|
||||
-- setEnabled(id, enabled): persist options.mods[id] in the exact shape
|
||||
-- Loader:_saveState writes (a plain boolean), so the running game and the
|
||||
-- in-game ManagerState pick it up unchanged.
|
||||
|
||||
Reference in New Issue
Block a user