diff --git a/src/ui/gen2/NamingScreen.lua b/src/ui/gen2/NamingScreen.lua index 4857499c..f60084c4 100644 --- a/src/ui/gen2/NamingScreen.lua +++ b/src/ui/gen2/NamingScreen.lua @@ -21,6 +21,7 @@ local Chrome = require("src.ui.gen2.Chrome") local Font = require("src.render.Font") local GbcPalette = require("src.render.GbcPalette") local Runtime = require("src.mods.Runtime") +local Strings = require("src.core.Strings") local NamingScreen = {} NamingScreen.__index = NamingScreen @@ -67,8 +68,15 @@ local BOX_INPUT_LOWER = { -- case target names the board it SWITCHES TO, not the one it is on: the last -- row of NameInputUpper is "lower DEL END" and the last row of -- NameInputLower is "UPPER DEL END" (data/text/name_input_chars.asm). -local BOTTOM_UPPER_LABELS = { "lower", "DEL", "END" } -local BOTTOM_LOWER_LABELS = { "UPPER", "DEL", "END" } +-- Wrapped in Strings.source, not Strings: both tables are built once at +-- require time, before any mod's Strings.load has a catalog to answer from +-- (src/core/Strings.lua's own note on this); drawPanel resolves them live. +local BOTTOM_UPPER_LABELS = { + Strings.source("lower"), Strings.source("DEL"), Strings.source("END"), +} +local BOTTOM_LOWER_LABELS = { + Strings.source("UPPER"), Strings.source("DEL"), Strings.source("END"), +} -- Cursor tile for each target: NamingScreen_AnimateCursor's .CaseDelEnd adds -- pixel $00 / $30 / $60 to the cursor's own XCOORD of 24 (`depixel 10, 3`), -- which is OAM x 24 / 72 / 120 and so screen tile 2 / 8 / 14. The bracket is @@ -81,10 +89,10 @@ local BOTTOM_CURSOR_TILES = 5 -- NAME_* types (constants/menu_constants.asm order) as prompts + field sizes. -- Lengths are the ASM's *_NAME_LENGTH - 1, i.e. usable characters. NamingScreen.TYPES = { - player = { prompt = "YOUR NAME?", maxLength = 7, sprite = "SPRITE_CHRIS" }, - rival = { prompt = "RIVAL'S NAME?", maxLength = 7, sprite = "SPRITE_RIVAL" }, - mom = { prompt = "MOTHER'S NAME?", maxLength = 7, sprite = "SPRITE_MOM" }, - box = { prompt = "BOX NAME?", maxLength = 8, isBox = true }, + player = { prompt = Strings.source("YOUR NAME?"), maxLength = 7, sprite = "SPRITE_CHRIS" }, + rival = { prompt = Strings.source("RIVAL'S NAME?"), maxLength = 7, sprite = "SPRITE_RIVAL" }, + mom = { prompt = Strings.source("MOTHER'S NAME?"), maxLength = 7, sprite = "SPRITE_MOM" }, + box = { prompt = Strings.source("BOX NAME?"), maxLength = 8, isBox = true }, nickname = { prompt = nil, maxLength = 10 }, } @@ -103,7 +111,7 @@ function NamingScreen.new(game, opts) self.kind = kind self.isBox = opts.isBox or kind.isBox or false self.maxLength = opts.maxLength or kind.maxLength or 7 - self.prompt = opts.prompt or kind.prompt or "NICKNAME?" + self.prompt = opts.prompt or kind.prompt or Strings.source("NICKNAME?") self.monName = opts.monName self.onDone = opts.onDone self.onCancel = opts.onCancel @@ -493,11 +501,15 @@ function NamingScreen:drawPanel() end local pal = self.palette if self.monName then - -- Nickname header is two lines: "'S" then "NICKNAME?". - Chrome.printThrough(self.monName .. "'S", 5, 2, pal) - Chrome.printThrough("NICKNAME?", 5, 4, pal) + -- Nickname header is two lines: "'S" then "NICKNAME?". Kept as two + -- Strings() calls, one per line (Chrome.printThrough draws a single row), + -- with the mon name folded into the first line's own format string so a + -- language whose possessive is not a bare suffix can restructure that + -- line rather than being stuck splicing one on. + Chrome.printThrough(Strings("%s'S", self.monName), 5, 2, pal) + Chrome.printThrough(Strings("NICKNAME?"), 5, 4, pal) else - Chrome.printThrough(self.prompt, 5, 2, pal) + Chrome.printThrough(Strings(self.prompt), 5, 2, pal) end self:drawEntry(5, self.isBox and 4 or 6) @@ -509,14 +521,18 @@ function NamingScreen:drawPanel() for col = 0, 8 do local ch = line[col + 1] if ch and ch ~= " " and ch ~= "" then - Chrome.printThrough(ch, 2 + col * 2, keyboardTop + row * 2, pal) + -- Same seam the Gen 1 board's cells go through (src/ui/NamingScreen + -- .lua's own Strings(cell)): a script whose alphabet does not fit + -- A-Z can swap a cell's glyph without needing the heavier + -- ui.naming.grid hook this screen also offers. + Chrome.printThrough(Strings(ch), 2 + col * 2, keyboardTop + row * 2, pal) end end end local labels = self.lower and BOTTOM_LOWER_LABELS or BOTTOM_UPPER_LABELS local bottomY = keyboardTop + bottom * 2 for i, label in ipairs(labels) do - Chrome.printThrough(label, BOTTOM_LABEL_TX[i], bottomY, pal) + Chrome.printThrough(Strings(label), BOTTOM_LABEL_TX[i], bottomY, pal) end local function cursor() self:drawCursorBox(self:cursorTile()) end diff --git a/tests/engine/gen2_naming_screen_translation_test.lua b/tests/engine/gen2_naming_screen_translation_test.lua new file mode 100644 index 00000000..cf285f7e --- /dev/null +++ b/tests/engine/gen2_naming_screen_translation_test.lua @@ -0,0 +1,100 @@ +-- Gold's naming/keyboard screen (src/ui/gen2/NamingScreen.lua) had zero +-- Strings() calls: every prompt (YOUR NAME?/RIVAL'S NAME?/MOTHER'S NAME?/ +-- BOX NAME?/NICKNAME?), the on-screen keyboard's own letters, and the +-- lower/UPPER/DEL/END bottom-row labels were bare literals, invisible to a +-- translation mod's `strings` registry (reported against a real Gold build, +-- gen1recomp#1642). The Gen 1 naming screen (src/ui/NamingScreen.lua) already +-- routes its title and every keyboard cell through Strings(). +-- +-- GbcPalette.available() is false headless (no real shader compiles), so +-- Chrome.printThrough already falls back to the plain, unshaded Chrome.print +-- -- this drives that path directly and checks the translated text reaches +-- Font.draw, the same technique +-- tests/engine/gen2_options_menu_translation_test.lua uses for the OPTION +-- screen. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +love = require("tests.love_stub") + +require("src.core.Logger").warn = function() end + +local drawn +package.loaded["src.render.Font"] = { + draw = function(text, x, y) + drawn[#drawn + 1] = { text = text, x = x, y = y } + end, + drawCode = function() end, + drawBox = function() end, +} + +local NamingScreen = require("src.ui.gen2.NamingScreen") +local Strings = require("src.core.Strings") + +local function drawnAt(x, y) + for _, d in ipairs(drawn) do + if d.x == x and d.y == y then return d.text end + end + return nil +end + +-- Chrome.print multiplies tile coordinates by 8 (src/ui/gen2/Chrome.lua); +-- the prompt lands at tile (5, 2), the keyboard's first cell at (2, 8). +local PROMPT_X, PROMPT_Y = 5 * 8, 2 * 8 +local FIRST_CELL_X, FIRST_CELL_Y = 2 * 8, 8 * 8 + +-- ---------------------------------------------- vanilla: no mod catalog +do + local screen = NamingScreen.new({}, { type = "player" }) + drawn = {} + screen:drawPanel() + T.eq(drawnAt(PROMPT_X, PROMPT_Y), "YOUR NAME?", + "the player-name prompt draws in English with no mod loaded") + T.eq(drawnAt(FIRST_CELL_X, FIRST_CELL_Y), "A", + "and the keyboard's first cell too") +end + +-- ------------------------------------------------- a translation mod's turn +do + Strings.load({ + strings = { + ["YOUR NAME?"] = "TON NOM?", + ["A"] = "À", + ["lower"] = "minusc", + ["END"] = "FIN", + ["%s'S"] = "DE %s", + ["NICKNAME?"] = "SURNOM?", + }, + }) + + local screen = NamingScreen.new({}, { type = "player" }) + drawn = {} + screen:drawPanel() + T.eq(drawnAt(PROMPT_X, PROMPT_Y), "TON NOM?", + "a mod catalog reaches the prompt") + T.eq(drawnAt(FIRST_CELL_X, FIRST_CELL_Y), "À", + "and a keyboard cell") + + -- The bottom row: lower/DEL/END at tile y = keyboardTop + bottom*2. + local bottomY = (screen:keyboardTop() + screen:bottomRow() * 2) * 8 + T.eq(drawnAt(2 * 8, bottomY), "minusc", "the case-switch label is translated") + T.eq(drawnAt(15 * 8, bottomY), "FIN", "and END, the way out of the screen") + + -- The nickname header: two lines, the mon name folded into the first. + local nickScreen = NamingScreen.new({}, { type = "nickname", monName = "BULBASAUR" }) + drawn = {} + nickScreen:drawPanel() + T.eq(drawnAt(PROMPT_X, PROMPT_Y), "DE BULBASAUR", + "the nickname header's first line takes the mod's own word order") + T.eq(drawnAt(PROMPT_X, 4 * 8), "SURNOM?", "and its second line") + + -- Module state is process-global (see tests/gen2_clock_test.lua's own + -- note); this suite gets its own process from tests/tier_runner.lua, but + -- leaving the catalog loaded past this point would still mistranslate + -- every check below it in this file. + Strings.load({}) + T.check(not Strings.active(), "the catalog is unloaded for the checks after this one") +end + +T.finish("gen2_naming_screen_translation_test")