From 5bc7d203a50df6e39a2595c224eb9d051a428eb0 Mon Sep 17 00:00:00 2001 From: Juan Heredia Date: Fri, 7 Aug 2026 13:27:59 +0200 Subject: [PATCH] Route the last hardcoded UI labels through the Strings catalog Wrap the battle stat box, the PC quantity footer and the dex metric labels in Strings() (SummaryMenu -- and MoveEffects since #811 -- already do this); give the battle menu a 'battle' lookup context so a translation can shorten FIGHT/ITEM/RUN independently of the pause menu; align the metric dex rows with the imperial ones and make the No. prefix translatable; make the title menu's recolor zone follow Menu's auto-grown width; honor the declared-but-unread boot.title seam, drawing an explicit versionRibbon as one centered piece. With an empty catalog every path is pixel-identical to vanilla. --- src/battle/BattleState.lua | 12 +++++------ src/ui/DexEntryMenu.lua | 6 +++--- src/ui/PlayerPC.lua | 2 +- src/ui/TitleState.lua | 42 +++++++++++++++++++++++++++++++------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index da79b2b7..52e98a91 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -424,7 +424,7 @@ function StatBox:draw() { Strings("SPEED"), s.speed }, { Strings("SPECIAL"), s.special } } for i, r in ipairs(rows) do - Font.draw(r[1], 88, 24 + (i - 1) * 16) + Font.draw(Strings(r[1]), 88, 24 + (i - 1) * 16) Font.draw(("%3d"):format(r[2]), 128, 32 + (i - 1) * 16) end love.graphics.setColor(1, 1, 1, 1) @@ -5587,9 +5587,9 @@ function BattleState:drawTextArea() -- -- next to FIGHT (9,14) for the first 80 frames, then ITEM (9,16) Font.drawBox(8, 12, 12, 6) love.graphics.setColor(0, 0, 0, 1) - Font.draw(Strings("FIGHT"), 80, 112) + Font.draw(Strings("FIGHT", "battle"), 80, 112) Font.drawCode(0xE1, 128, 112); Font.drawCode(0xE2, 136, 112) - Font.draw(Strings("ITEM"), 80, 128); Font.draw(Strings("RUN"), 128, 128) + Font.draw(Strings("ITEM", "battle"), 80, 128); Font.draw(Strings("RUN", "battle"), 128, 128) Font.drawCode(0xED, 72, (self.demoTimer or 0) <= 80 and 112 or 128) elseif self.phase == "menu" then local col = (self.menuIndex - 1) % 2 @@ -5599,7 +5599,7 @@ function BattleState:drawTextArea() -- THROW ROCK RUN" from (2,14) Font.drawBox(0, 12, 20, 6) Font.draw(Strings("BALLx"), 16, 112); Font.draw(Strings("BAIT"), 112, 112) - Font.draw(Strings("THROW ROCK"), 16, 128); Font.draw(Strings("RUN"), 112, 128) + Font.draw(Strings("THROW ROCK"), 16, 128); Font.draw(Strings("RUN", "battle"), 112, 128) -- DisplayBattleMenu .safariLeftColumn / .safariRightColumn print -- wNumSafariBalls at hlcoord 7,14 with `lb bc, 1, 2` -- one byte, two -- digits, space padded -- right after the "BALLx" label at columns @@ -5610,9 +5610,9 @@ function BattleState:drawTextArea() -- BATTLE_MENU_TEMPLATE: box (8,12)-(19,17), "FIGHT / -- ITEM RUN" from (10,14); cursor columns 9 / 15 Font.drawBox(8, 12, 12, 6) - Font.draw(Strings("FIGHT"), 80, 112) + Font.draw(Strings("FIGHT", "battle"), 80, 112) Font.drawCode(0xE1, 128, 112); Font.drawCode(0xE2, 136, 112) - Font.draw(Strings("ITEM"), 80, 128); Font.draw(Strings("RUN"), 128, 128) + Font.draw(Strings("ITEM", "battle"), 80, 128); Font.draw(Strings("RUN", "battle"), 128, 128) Font.drawCode(0xED, (col == 0 and 72 or 120), 112 + row * 16) end elseif self.phase == "moveSelect" then diff --git a/src/ui/DexEntryMenu.lua b/src/ui/DexEntryMenu.lua index b59e6ee1..f5f1a09d 100644 --- a/src/ui/DexEntryMenu.lua +++ b/src/ui/DexEntryMenu.lua @@ -94,7 +94,7 @@ function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor) -- same number width as the list (constants.dexDigits), so a dex past 999 -- prints the extra digit everywhere at once local digits = (game.data.constants or {}).dexDigits or 3 - Font.draw(("No.%0" .. digits .. "d"):format(def.dex or 0), 72, 32) + Font.draw(Strings("No.") .. ("%0" .. digits .. "d"):format(def.dex or 0), 72, 32) local owned = forceOwned or (game.save.pokedex and game.save.pokedex.owned[def.id]) -- height/weight print only once owned, like the description @@ -105,8 +105,8 @@ function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor) -- pokedex.asm; the tiles come from gfx/pokedex/pokedex.png via -- engine/gfx/load_pokedex_tiles.asm) if e.heightM then - Font.draw((("GR. %.1fm"):format(e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 64, 44) - Font.draw((("GEW. %.1fkg"):format(e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 64, 54) + Font.draw((Strings("GR. %.1fm", e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 72, 44) + Font.draw((Strings("GEW. %.1fkg", e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 72, 54) else Font.draw(Strings("HT %d′%02d″", e.heightFt, e.heightIn or 0), 72, 44) Font.draw(Strings("WT %.1flb", (e.weight or 0) / 10), 72, 54) diff --git a/src/ui/PlayerPC.lua b/src/ui/PlayerPC.lua index 5e31ca39..6fdb6b90 100644 --- a/src/ui/PlayerPC.lua +++ b/src/ui/PlayerPC.lua @@ -44,7 +44,7 @@ local function askQuantity(game, list, count, id, cb) cb(1) return end - list.footer = "How many?" + list.footer = Strings("How many?") local QuantityBox = require("src.ui.QuantityBox") game.stack:push(QuantityBox.new(game, { max = count, diff --git a/src/ui/TitleState.lua b/src/ui/TitleState.lua index f7d3ffe5..6ee89618 100644 --- a/src/ui/TitleState.lua +++ b/src/ui/TitleState.lua @@ -107,7 +107,12 @@ local CYCLE_FRAMES = 240 -- the original waits ~4s between picks local function tryImage(path) if not path then return nil end - local ok, img = pcall(love.graphics.newImage, path) + -- resolve through Assets so a mod's derived art (save/mod-derived/) + -- wins here the way it does for every other generated sheet -- but + -- load uncached, because on NX the per-version overlay redirects the + -- open itself and a cached image would leak across Yellow/Blue boots + local ok, img = pcall(love.graphics.newImage, + require("src.render.Assets").resolve(path)) return ok and img or nil end @@ -151,10 +156,24 @@ function TitleState.new(game, opts) -- branding comes from field.title with the shipped art as fallback, so -- a total conversion rebrands the title without replacing the screen local title = (game.data.field and game.data.field.title) or {} + -- field.title itself is extraction data the field schema never exposes; + -- boot.title is the mod-reachable half of the same seam, so its keys + -- override here (a localized ribbon, a rebranded logo) + local boot = game.data.field and game.data.field.boot + if boot and type(boot.title) == "table" then + local merged = {} + for key, value in pairs(title) do merged[key] = value end + for key, value in pairs(boot.title) do merged[key] = value end + title = merged + end self.title = title self.logo = tryImage(imagePath(title.logo) or "assets/logo/pokemon_logo.png") - -- versionRibbon is the file-12 key; version is the importer's + -- versionRibbon is the file-12 key; version is the importer's. The + -- vanilla sheet is two fragments the draw pass repositions, so an + -- explicit ribbon (a conversion's or a translation's continuous art) + -- draws whole instead. + self.versionFull = imagePath(title.versionRibbon) ~= nil self.version = tryImage(imagePath(title.versionRibbon or title.version) or "assets/generated/title/red_version.png") self.player = tryImage("assets/generated/title/player.png") @@ -347,8 +366,12 @@ function ContinueInfo:draw() -- box at (4,7), 8x14 content; labels double-spaced from (5,9) Font.drawBox(4, 7, 16, 10) love.graphics.setColor(0, 0, 0, 1) - Font.draw(Strings("PLAYER"), 40, 72) - Font.draw((save.player and save.player.name) or "RED", 96, 72) + -- the name follows the label's real width (one space after it), so a + -- localized label longer than PLAYER's six glyphs cannot run into it + local playerLabel = Strings("PLAYER") + Font.draw(playerLabel, 40, 72) + Font.draw((save.player and save.player.name) or "RED", + math.max(96, 40 + (#Font.split(playerLabel) + 1) * 8), 72) local badges = require("src.inventory.Badges").count(self.game.data, save) Font.draw(Strings("BADGES"), 40, 88) Font.draw(("%2d"):format(badges), 128, 88) @@ -401,8 +424,10 @@ function TitleState:openMenu() end local th = #items * 2 + 2 local menu = Menu.new(game, items, { tx = 0, ty = 0, tw = 13, th = th }) - -- full-width title LOGO zones would recolor this box; see sgbPalettes - menu.titleUiBox = { 0, 0, 12, th - 1 } + -- full-width title LOGO zones would recolor this box; see sgbPalettes. + -- Menu.new may have grown tw for longer (e.g. localized) labels, so the + -- recolor zone follows the box's real width instead of the vanilla 13. + menu.titleUiBox = { 0, 0, menu.tw - 1, th - 1 } game.stack:push(menu) end @@ -490,7 +515,10 @@ function TitleState:draw() -- the Yellow fallback layout draws no ribbon at all. if self.version and not self.yellow then local iw, ih = self.version:getDimensions() - if self.blue then + if self.versionFull then + -- a continuous ribbon (versionRibbon) centers as one piece + love.graphics.draw(self.version, math.floor((160 - iw) / 2), 64) + elseif self.blue then love.graphics.draw(self.version, love.graphics.newQuad(0, 0, 64, 8, iw, ih), 56, 64) else