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.
This commit is contained in:
Juan Heredia
2026-08-07 13:27:59 +02:00
parent 112120e8fe
commit 5bc7d203a5
4 changed files with 45 additions and 17 deletions
+6 -6
View File
@@ -424,7 +424,7 @@ function StatBox:draw()
{ Strings("SPEED"), s.speed }, { Strings("SPEED"), s.speed },
{ Strings("SPECIAL"), s.special } } { Strings("SPECIAL"), s.special } }
for i, r in ipairs(rows) do 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) Font.draw(("%3d"):format(r[2]), 128, 32 + (i - 1) * 16)
end end
love.graphics.setColor(1, 1, 1, 1) 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) -- -- next to FIGHT (9,14) for the first 80 frames, then ITEM (9,16)
Font.drawBox(8, 12, 12, 6) Font.drawBox(8, 12, 12, 6)
love.graphics.setColor(0, 0, 0, 1) 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.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) Font.drawCode(0xED, 72, (self.demoTimer or 0) <= 80 and 112 or 128)
elseif self.phase == "menu" then elseif self.phase == "menu" then
local col = (self.menuIndex - 1) % 2 local col = (self.menuIndex - 1) % 2
@@ -5599,7 +5599,7 @@ function BattleState:drawTextArea()
-- THROW ROCK RUN" from (2,14) -- THROW ROCK RUN" from (2,14)
Font.drawBox(0, 12, 20, 6) Font.drawBox(0, 12, 20, 6)
Font.draw(Strings("BALLx"), 16, 112); Font.draw(Strings("BAIT"), 112, 112) 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 -- DisplayBattleMenu .safariLeftColumn / .safariRightColumn print
-- wNumSafariBalls at hlcoord 7,14 with `lb bc, 1, 2` -- one byte, two -- wNumSafariBalls at hlcoord 7,14 with `lb bc, 1, 2` -- one byte, two
-- digits, space padded -- right after the "BALLx" label at columns -- 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 <PK><MN> / -- BATTLE_MENU_TEMPLATE: box (8,12)-(19,17), "FIGHT <PK><MN> /
-- ITEM RUN" from (10,14); cursor columns 9 / 15 -- ITEM RUN" from (10,14); cursor columns 9 / 15
Font.drawBox(8, 12, 12, 6) 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.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) Font.drawCode(0xED, (col == 0 and 72 or 120), 112 + row * 16)
end end
elseif self.phase == "moveSelect" then elseif self.phase == "moveSelect" then
+3 -3
View File
@@ -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 -- same number width as the list (constants.dexDigits), so a dex past 999
-- prints the extra digit everywhere at once -- prints the extra digit everywhere at once
local digits = (game.data.constants or {}).dexDigits or 3 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 local owned = forceOwned
or (game.save.pokedex and game.save.pokedex.owned[def.id]) or (game.save.pokedex and game.save.pokedex.owned[def.id])
-- height/weight print only once owned, like the description -- 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 -- pokedex.asm; the tiles come from gfx/pokedex/pokedex.png via
-- engine/gfx/load_pokedex_tiles.asm) -- engine/gfx/load_pokedex_tiles.asm)
if e.heightM then if e.heightM then
Font.draw((("GR. %.1fm"):format(e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 64, 44) Font.draw((Strings("GR. %.1fm", e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 72, 44)
Font.draw((("GEW. %.1fkg"):format(e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 64, 54) Font.draw((Strings("GEW. %.1fkg", e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 72, 54)
else else
Font.draw(Strings("HT %d%02d″", e.heightFt, e.heightIn or 0), 72, 44) 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) Font.draw(Strings("WT %.1flb", (e.weight or 0) / 10), 72, 54)
+1 -1
View File
@@ -44,7 +44,7 @@ local function askQuantity(game, list, count, id, cb)
cb(1) cb(1)
return return
end end
list.footer = "How many?" list.footer = Strings("How many?")
local QuantityBox = require("src.ui.QuantityBox") local QuantityBox = require("src.ui.QuantityBox")
game.stack:push(QuantityBox.new(game, { game.stack:push(QuantityBox.new(game, {
max = count, max = count,
+35 -7
View File
@@ -107,7 +107,12 @@ local CYCLE_FRAMES = 240 -- the original waits ~4s between picks
local function tryImage(path) local function tryImage(path)
if not path then return nil end 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 return ok and img or nil
end end
@@ -151,10 +156,24 @@ function TitleState.new(game, opts)
-- branding comes from field.title with the shipped art as fallback, so -- branding comes from field.title with the shipped art as fallback, so
-- a total conversion rebrands the title without replacing the screen -- a total conversion rebrands the title without replacing the screen
local title = (game.data.field and game.data.field.title) or {} 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.title = title
self.logo = tryImage(imagePath(title.logo) self.logo = tryImage(imagePath(title.logo)
or "assets/logo/pokemon_logo.png") 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) self.version = tryImage(imagePath(title.versionRibbon or title.version)
or "assets/generated/title/red_version.png") or "assets/generated/title/red_version.png")
self.player = tryImage("assets/generated/title/player.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) -- box at (4,7), 8x14 content; labels double-spaced from (5,9)
Font.drawBox(4, 7, 16, 10) Font.drawBox(4, 7, 16, 10)
love.graphics.setColor(0, 0, 0, 1) love.graphics.setColor(0, 0, 0, 1)
Font.draw(Strings("PLAYER"), 40, 72) -- the name follows the label's real width (one space after it), so a
Font.draw((save.player and save.player.name) or "RED", 96, 72) -- 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) local badges = require("src.inventory.Badges").count(self.game.data, save)
Font.draw(Strings("BADGES"), 40, 88) Font.draw(Strings("BADGES"), 40, 88)
Font.draw(("%2d"):format(badges), 128, 88) Font.draw(("%2d"):format(badges), 128, 88)
@@ -401,8 +424,10 @@ function TitleState:openMenu()
end end
local th = #items * 2 + 2 local th = #items * 2 + 2
local menu = Menu.new(game, items, { tx = 0, ty = 0, tw = 13, th = th }) 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 -- full-width title LOGO zones would recolor this box; see sgbPalettes.
menu.titleUiBox = { 0, 0, 12, th - 1 } -- 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) game.stack:push(menu)
end end
@@ -490,7 +515,10 @@ function TitleState:draw()
-- the Yellow fallback layout draws no ribbon at all. -- the Yellow fallback layout draws no ribbon at all.
if self.version and not self.yellow then if self.version and not self.yellow then
local iw, ih = self.version:getDimensions() 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.draw(self.version,
love.graphics.newQuad(0, 0, 64, 8, iw, ih), 56, 64) love.graphics.newQuad(0, 0, 64, 8, iw, ih), 56, 64)
else else