Route Gold's title menu and SAVE screen through Strings()

MainMenu.lua's row labels (CONTINUE/NEW GAME/OPTION/EXIT GAME), its clock
box's AM/PM half, and its CONTINUE save-summary panel (PLAYER <name>/
BADGES/POKéDEX/TIME, or NO SAVE FILE) were bare literals, invisible to a
translation mod's strings registry. SaveMenu.lua's confirm/overwrite/
saving/saved prompts, its YES/NO choice, and its own copy of the same
summary panel had the same gap. Both mirror the Gen 1 port's already-
translated equivalents (src/ui/TitleState.lua, src/ui/StartMenu.lua),
which route every one of these rows through Strings().

SaveMenu's two-line prompts (module-level OVERWRITE_PROMPT/SAVING_PROMPT,
plus the dynamically-built "%s saved\nthe game." and the confirm/failed
messages) are now single Strings()-resolved strings with an embedded "\n",
matching the Gen 1 port's own single-call convention for two-line messages,
split into the two-slot table drawPanel's fixed-position Chrome.print calls
expect only at draw time -- so a translation sees one whole sentence to
reorder, not two independently-translated fragments.

Added tests/engine/gen2_main_menu_translation_test.lua and
gen2_save_menu_translation_test.lua: drive both screens' drawPanel()/
drawSavePanel() with a mod-loaded Strings catalog and check the translated
text reaches Font.draw, plus a vanilla no-mod case proving the fallback is
unchanged. Confirmed both catch the regression: reverting either file to
its pre-fix content fails the corresponding suite (7 and 15 checks
respectively).
This commit is contained in:
thibautbus
2026-08-23 17:57:10 +02:00
parent 478e3bf8eb
commit fad7443f94
4 changed files with 343 additions and 22 deletions
+11 -10
View File
@@ -24,6 +24,7 @@ local Logger = require("src.core.Logger")
local Music = require("src.core.Music") local Music = require("src.core.Music")
local Runtime = require("src.mods.Runtime") local Runtime = require("src.mods.Runtime")
local Save = require("src.core.gen2.Save") local Save = require("src.core.gen2.Save")
local Strings = require("src.core.Strings")
local MainMenu = {} local MainMenu = {}
MainMenu.__index = MainMenu MainMenu.__index = MainMenu
@@ -73,14 +74,14 @@ local function sameItems(_, items) return items end
function MainMenu:buildList() function MainMenu:buildList()
local items = {} local items = {}
if self.hasSave then if self.hasSave then
items[#items + 1] = { label = "CONTINUE", value = "continue" } items[#items + 1] = { label = Strings("CONTINUE"), value = "continue" }
end end
items[#items + 1] = { label = "NEW GAME", value = "new" } items[#items + 1] = { label = Strings("NEW GAME"), value = "new" }
items[#items + 1] = { label = "OPTION", value = "option" } items[#items + 1] = { label = Strings("OPTION"), value = "option" }
-- Not on the cart: a cartridge is left by switching the console off, and -- Not on the cart: a cartridge is left by switching the console off, and
-- there is no console here. Mirrors the Gen 1 port's title menu -- there is no console here. Mirrors the Gen 1 port's title menu
-- (src/ui/TitleState.lua), which adds the same row for the same reason. -- (src/ui/TitleState.lua), which adds the same row for the same reason.
items[#items + 1] = { label = "EXIT GAME", value = "exit" } items[#items + 1] = { label = Strings("EXIT GAME"), value = "exit" }
-- The same hook name and the same (game, items) payload the Gen 1 title -- The same hook name and the same (game, items) payload the Gen 1 title
-- menu raises (src/ui/TitleState.lua:openMenu), so one mod's title rows -- menu raises (src/ui/TitleState.lua:openMenu), so one mod's title rows
-- serve both games; only the row shape differs, because Chrome.List reads -- serve both games; only the row shape differs, because Chrome.List reads
@@ -170,7 +171,7 @@ function MainMenu:drawClockBox()
-- minutes; the AM/PM half is drawn by PrintHour itself. -- minutes; the AM/PM half is drawn by PrintHour itself.
local display = hour % 12 local display = hour % 12
if display == 0 then display = 12 end if display == 0 then display = 12 end
local half = hour < 12 and "AM" or "PM" local half = Strings(hour < 12 and "AM" or "PM")
Chrome.print(("%s:%s %s"):format( Chrome.print(("%s:%s %s"):format(
Chrome.number(display, 2), Chrome.number(minute, 2, true), half), 4, 16) Chrome.number(display, 2), Chrome.number(minute, 2, true), half), 4, 16)
end end
@@ -180,15 +181,15 @@ function MainMenu:drawSavePanel()
-- DisplaySaveInfoOnContinue: a box down the right side listing the trainer. -- DisplaySaveInfoOnContinue: a box down the right side listing the trainer.
Chrome.textbox(4, 0, 14, 9) Chrome.textbox(4, 0, 14, 9)
if not summary then if not summary then
Chrome.print("NO SAVE FILE", 5, 2) Chrome.print(Strings("NO SAVE FILE"), 5, 2)
return return
end end
Chrome.print("PLAYER " .. summary.name, 5, 2) Chrome.print(Strings("PLAYER %s", summary.name), 5, 2)
Chrome.print("BADGES", 5, 4) Chrome.print(Strings("BADGES"), 5, 4)
Chrome.printRight(tostring(summary.badges), 17, 4) Chrome.printRight(tostring(summary.badges), 17, 4)
Chrome.print("POKéDEX", 5, 6) Chrome.print(Strings("POKéDEX"), 5, 6)
Chrome.printRight(tostring(summary.caught), 17, 6) Chrome.printRight(tostring(summary.caught), 17, 6)
Chrome.print("TIME", 5, 8) Chrome.print(Strings("TIME"), 5, 8)
Chrome.printRight(("%d:%s"):format( Chrome.printRight(("%d:%s"):format(
summary.hours, Chrome.number(summary.minutes, 2, true)), 17, 8) summary.hours, Chrome.number(summary.minutes, 2, true)), 17, 8)
end end
+46 -12
View File
@@ -26,6 +26,7 @@
local Chrome = require("src.ui.gen2.Chrome") local Chrome = require("src.ui.gen2.Chrome")
local Save = require("src.core.gen2.Save") local Save = require("src.core.gen2.Save")
local Sound = require("src.core.Sound") local Sound = require("src.core.Sound")
local Strings = require("src.core.Strings")
local SaveMenu = {} local SaveMenu = {}
SaveMenu.__index = SaveMenu SaveMenu.__index = SaveMenu
@@ -55,10 +56,43 @@ local TIME_X, TIME_Y = 13, 8
local YESNO_X, YESNO_Y, YESNO_W, YESNO_H = 0, 7, 6, 5 local YESNO_X, YESNO_Y, YESNO_W, YESNO_H = 0, 7, 6, 5
-- AlreadyASaveFileText (AskOverwriteSaveFile, engine/menus/save.asm:47) and -- AlreadyASaveFileText (AskOverwriteSaveFile, engine/menus/save.asm:47) and
-- SavingDontTurnOffThePower's own line, shared with the PC's CHANGE BOX save. -- SavingDontTurnOffThePower's own line, shared with the PC's CHANGE BOX save
-- (src/ui/gen2/PcMenu.lua:savePrompt(), which returns this two-slot table
-- straight through to its own lines[1]/lines[2] Chrome.print calls, so the
-- table shape here is a cross-file contract that must not change).
local OVERWRITE_PROMPT = { "There is already a", "save file. Is it" } local OVERWRITE_PROMPT = { "There is already a", "save file. Is it" }
local SAVING_PROMPT = { "SAVING… DON'T TURN", "OFF THE POWER." } local SAVING_PROMPT = { "SAVING… DON'T TURN", "OFF THE POWER." }
-- The same two prompts as a single \n-joined Strings.source() key, used only
-- by this screen's own prompt() below (PcMenu keeps reading the untranslated
-- table above unchanged). One key lets a translation reorder the whole
-- sentence rather than two independently-translated fragments, and lets a
-- cart whose own translation shows it on ONE line (German's SAVING prompt
-- has no second line at all) say so directly -- the per-line "{RAM:...}"-
-- style split load_engine_overrides uses elsewhere requires a non-empty
-- override for every line, so it cannot express "this line is blank" the
-- way an embedded "\n"-less string can.
--
-- Written as literals, not `table.concat(OVERWRITE_PROMPT, "\n")`: tools/
-- modkit.py's STRINGS_CALL harvester matches a quoted string literal
-- immediately inside Strings.source(...)/Strings(...), not an arbitrary
-- expression, so a computed argument here would be invisible to every
-- translator's `modkit.py translation ... --refresh` scaffold despite the
-- runtime lookup working fine -- caught by an independent review. Keep
-- these two byte-for-byte in sync with OVERWRITE_PROMPT/SAVING_PROMPT
-- above (checked by tests/engine/gen2_save_menu_translation_test.lua).
local OVERWRITE_PROMPT_SOURCE = Strings.source("There is already a\nsave file. Is it")
local SAVING_PROMPT_SOURCE = Strings.source("SAVING… DON'T TURN\nOFF THE POWER.")
-- Splits a Strings()-resolved "line one\nline two" into the two-slot table
-- drawPanel's fixed-position Chrome.print calls expect; a translation with no
-- "\n" at all (single-line messages like "Could not save.") lands whole on
-- the first slot, matching the untranslated code's own { text, "" } shape.
local function twoLines(text)
local first, second = text:match("^(.-)\n(.*)$")
return { first or text, second or "" }
end
function SaveMenu:wantsFillScale() return true end function SaveMenu:wantsFillScale() return true end
function SaveMenu:drawsWidescreen() return true end function SaveMenu:drawsWidescreen() return true end
@@ -171,18 +205,18 @@ function SaveMenu:prompt()
if self.phase == "overwrite" then if self.phase == "overwrite" then
-- AlreadyASaveFileText when the file is this player's; AnotherSaveFileText -- AlreadyASaveFileText when the file is this player's; AnotherSaveFileText
-- when the ID differs. Only the first can happen here. -- when the ID differs. Only the first can happen here.
return OVERWRITE_PROMPT return twoLines(Strings(OVERWRITE_PROMPT_SOURCE))
end end
if self.phase == "saving" then if self.phase == "saving" then
return SAVING_PROMPT return twoLines(Strings(SAVING_PROMPT_SOURCE))
end end
if self.phase == "done" then if self.phase == "done" then
if self.saved then if self.saved then
return { self:playerName() .. " saved", "the game." } return twoLines(Strings("%s saved\nthe game.", self:playerName()))
end end
return { "Could not save.", "" } return twoLines(Strings("Could not save."))
end end
return { "Would you like to", "save the game?" } return twoLines(Strings("Would you like to\nsave the game?"))
end end
function SaveMenu:drawPanel() function SaveMenu:drawPanel()
@@ -190,10 +224,10 @@ function SaveMenu:drawPanel()
local summary = Save.summary(self.save) local summary = Save.summary(self.save)
Chrome.box(PANEL_X, PANEL_Y, PANEL_W, PANEL_H) Chrome.box(PANEL_X, PANEL_Y, PANEL_W, PANEL_H)
if summary then if summary then
Chrome.print("PLAYER " .. summary.name, LABEL_X, LABEL_Y) Chrome.print(Strings("PLAYER %s", summary.name), LABEL_X, LABEL_Y)
Chrome.print("BADGES", LABEL_X, LABEL_Y + 2) Chrome.print(Strings("BADGES"), LABEL_X, LABEL_Y + 2)
Chrome.print("POKéDEX", LABEL_X, LABEL_Y + 4) Chrome.print(Strings("POKéDEX"), LABEL_X, LABEL_Y + 4)
Chrome.print("TIME", LABEL_X, LABEL_Y + 6) Chrome.print(Strings("TIME"), LABEL_X, LABEL_Y + 6)
-- PrintNum fills its field from the left, space padded. -- PrintNum fills its field from the left, space padded.
Chrome.print(Chrome.number(summary.badges, 2), BADGES_X, BADGES_Y) Chrome.print(Chrome.number(summary.badges, 2), BADGES_X, BADGES_Y)
Chrome.print(Chrome.number(summary.caught, 3), DEX_X, DEX_Y) Chrome.print(Chrome.number(summary.caught, 3), DEX_X, DEX_Y)
@@ -211,8 +245,8 @@ function SaveMenu:drawPanel()
if self.phase == "confirm" or self.phase == "overwrite" then if self.phase == "confirm" or self.phase == "overwrite" then
Chrome.box(YESNO_X, YESNO_Y, YESNO_W, YESNO_H) Chrome.box(YESNO_X, YESNO_Y, YESNO_W, YESNO_H)
Chrome.print("YES", YESNO_X + 2, YESNO_Y + 1) Chrome.print(Strings("YES"), YESNO_X + 2, YESNO_Y + 1)
Chrome.print("NO", YESNO_X + 2, YESNO_Y + 3) Chrome.print(Strings("NO"), YESNO_X + 2, YESNO_Y + 3)
Chrome.cursor(YESNO_X + 1, YESNO_Y + (self.choice == 1 and 1 or 3)) Chrome.cursor(YESNO_X + 1, YESNO_Y + (self.choice == 1 and 1 or 3))
end end
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
@@ -0,0 +1,119 @@
-- Gold's title/main menu (src/ui/gen2/MainMenu.lua) drew every row label
-- (CONTINUE/NEW GAME/OPTION/EXIT GAME), the clock box's AM/PM half, and the
-- CONTINUE save-summary panel's labels (PLAYER <name>/BADGES/POKéDEX/TIME,
-- or NO SAVE FILE) as bare literals, invisible to a translation mod's
-- `strings` registry -- unlike the Gen 1 port's own title menu
-- (src/ui/TitleState.lua/StartMenu.lua), which already routes the same rows
-- through Strings(). Drives MainMenu:drawPanel()/:drawSavePanel() with a
-- mod-loaded Strings catalog and checks the translated text reaches
-- Font.draw, same technique as
-- tests/engine/gen2_naming_screen_translation_test.lua.
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,
width = function() return 0 end,
}
local MainMenu = require("src.ui.gen2.MainMenu")
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).
-- List item 1 lands at (self.x, self.y) = (2, 2); the clock box's day name
-- at (1, 14) and the hour:minute half at (4, 16); the save panel's PLAYER
-- row at (5, 2).
local FIRST_ITEM_X, FIRST_ITEM_Y = 2 * 8, 2 * 8
local CLOCK_HALF_X, CLOCK_HALF_Y = 4 * 8, 16 * 8
local PANEL_PLAYER_X, PANEL_PLAYER_Y = 5 * 8, 2 * 8
local SAVE = { player = { name = "GOLD" } }
local CLOCK = { hour = 13, minute = 5, weekday = 1 } -- 1 PM, SUNDAY
-- ---------------------------------------------- vanilla: no mod catalog
do
local menu = MainMenu.new({}, { hasSave = true, save = SAVE, clock = CLOCK })
drawn = {}
menu:drawPanel()
T.eq(drawnAt(FIRST_ITEM_X, FIRST_ITEM_Y), "CONTINUE",
"the title menu's first row draws in English with no mod loaded")
T.eq(drawnAt(CLOCK_HALF_X, CLOCK_HALF_Y), " 1:05 PM",
"and the clock box's AM/PM half")
drawn = {}
menu:drawSavePanel()
T.eq(drawnAt(PANEL_PLAYER_X, PANEL_PLAYER_Y), "PLAYER GOLD",
"the CONTINUE save-summary panel too")
local noSaveMenu = MainMenu.new({}, { hasSave = false, save = false, clock = CLOCK })
drawn = {}
noSaveMenu:drawSavePanel()
T.eq(drawnAt(PANEL_PLAYER_X, PANEL_PLAYER_Y), "NO SAVE FILE",
"and its no-summary fallback")
end
-- ------------------------------------------------- a translation mod's turn
do
Strings.load({
strings = {
["CONTINUE"] = "CONTINUAR",
["NEW GAME"] = "NUEVA PARTIDA",
["OPTION"] = "OPCIÓN",
["EXIT GAME"] = "SALIR",
["PM"] = "PM_ES",
["PLAYER %s"] = "JUGADOR %s",
["BADGES"] = "MEDALLAS",
["POKéDEX"] = "POKéDEX_ES",
["TIME"] = "TIEMPO",
["NO SAVE FILE"] = "SIN PARTIDA",
},
})
local menu = MainMenu.new({}, { hasSave = true, save = SAVE, clock = CLOCK })
drawn = {}
menu:drawPanel()
T.eq(drawnAt(FIRST_ITEM_X, FIRST_ITEM_Y), "CONTINUAR",
"a mod catalog reaches the title menu's first row")
T.eq(drawnAt(CLOCK_HALF_X, CLOCK_HALF_Y), " 1:05 PM_ES",
"and the clock box's AM/PM half")
drawn = {}
menu:drawSavePanel()
T.eq(drawnAt(PANEL_PLAYER_X, PANEL_PLAYER_Y), "JUGADOR GOLD",
"the save-summary panel's PLAYER row takes the mod's own word order")
T.eq(drawnAt(5 * 8, 4 * 8), "MEDALLAS", "and BADGES")
T.eq(drawnAt(5 * 8, 6 * 8), "POKéDEX_ES", "and POKéDEX")
T.eq(drawnAt(5 * 8, 8 * 8), "TIEMPO", "and TIME")
local noSaveMenu = MainMenu.new({}, { hasSave = false, save = false, clock = CLOCK })
drawn = {}
noSaveMenu:drawSavePanel()
T.eq(drawnAt(PANEL_PLAYER_X, PANEL_PLAYER_Y), "SIN PARTIDA",
"and the no-summary fallback")
-- 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_main_menu_translation_test")
@@ -0,0 +1,167 @@
-- Gold's SAVE screen (src/ui/gen2/SaveMenu.lua) drew every prompt ("Would
-- you like to save the game?", the overwrite/saving/saved messages), the
-- YES/NO choice, and the summary panel's labels (PLAYER <name>/BADGES/
-- POKéDEX/TIME) as bare literals, invisible to a translation mod's
-- `strings` registry -- unlike the Gen 1 port's own SAVE screen
-- (src/ui/StartMenu.lua), which already routes the same rows through
-- Strings(). Drives SaveMenu:drawPanel() directly at each phase with a
-- mod-loaded Strings catalog and checks the translated text reaches
-- Font.draw, same technique as
-- tests/engine/gen2_naming_screen_translation_test.lua.
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,
width = function() return 0 end,
}
local SaveMenu = require("src.ui.gen2.SaveMenu")
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).
-- PLAYER row at (5, 2); the two prompt lines at (1, 14)/(1, 16); YES/NO at
-- (2, 8)/(2, 10) (YESNO_X + 2, YESNO_Y + 1 / + 3).
local PANEL_PLAYER_X, PANEL_PLAYER_Y = 5 * 8, 2 * 8
local PROMPT1_X, PROMPT1_Y = 1 * 8, 14 * 8
local PROMPT2_X, PROMPT2_Y = 1 * 8, 16 * 8
local YES_X, YES_Y = 2 * 8, 8 * 8
local NO_X, NO_Y = 2 * 8, 10 * 8
local SAVE = { player = { name = "GOLD" } }
-- ---------------------------------------------- vanilla: no mod catalog
do
local menu = SaveMenu.new({}, { save = SAVE, existed = false })
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PANEL_PLAYER_X, PANEL_PLAYER_Y), "PLAYER GOLD",
"the summary panel draws in English with no mod loaded")
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "Would you like to",
"and the confirm prompt's first line")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "save the game?", "and its second line")
T.eq(drawnAt(YES_X, YES_Y), "YES", "and YES")
T.eq(drawnAt(NO_X, NO_Y), "NO", "and NO")
menu.phase = "overwrite"
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "There is already a", "the overwrite prompt")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "save file. Is it", "its second line")
menu.phase = "saving"
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "SAVING… DON'T TURN", "the saving message")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "OFF THE POWER.", "its second line")
menu.phase, menu.saved = "done", true
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "GOLD saved", "the saved message")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "the game.", "its second line")
menu.phase, menu.saved = "done", false
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "Could not save.", "the failed-save message")
end
-- ------------------------------------------------- a translation mod's turn
do
Strings.load({
strings = {
["PLAYER %s"] = "JOUEUR %s",
["BADGES"] = "BADGES_FR",
["POKéDEX"] = "POKéDEX_FR",
["TIME"] = "TEMPS",
["YES"] = "OUI",
["NO"] = "NON",
["Would you like to\nsave the game?"] = "Voulez-vous\nsauvegarder ?",
["There is already a\nsave file. Is it"] = "Un fichier existe\ndeja. Est-ce",
["SAVING… DON'T TURN\nOFF THE POWER."] = "SAUVEGARDE...\nN'ETEIGNEZ PAS.",
["%s saved\nthe game."] = "%s a sauvegarde\nla partie.",
["Could not save."] = "Echec de sauvegarde.",
},
})
local menu = SaveMenu.new({}, { save = SAVE, existed = false })
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PANEL_PLAYER_X, PANEL_PLAYER_Y), "JOUEUR GOLD",
"the summary panel's PLAYER row takes the mod's own word order")
T.eq(drawnAt(5 * 8, 4 * 8), "BADGES_FR", "and BADGES")
T.eq(drawnAt(5 * 8, 6 * 8), "POKéDEX_FR", "and POKéDEX")
T.eq(drawnAt(5 * 8, 8 * 8), "TEMPS", "and TIME")
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "Voulez-vous", "the confirm prompt")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "sauvegarder ?", "its second line")
T.eq(drawnAt(YES_X, YES_Y), "OUI", "and YES")
T.eq(drawnAt(NO_X, NO_Y), "NON", "and NO")
menu.phase = "overwrite"
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "Un fichier existe", "the overwrite prompt")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "deja. Est-ce", "its second line")
menu.phase = "saving"
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "SAUVEGARDE...", "the saving message")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "N'ETEIGNEZ PAS.", "its second line")
menu.phase, menu.saved = "done", true
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "GOLD a sauvegarde",
"the saved message folds the player name into the mod's own word order")
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "la partie.", "its second line")
menu.phase, menu.saved = "done", false
drawn = {}
menu:drawPanel()
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "Echec de sauvegarde.", "the failed-save message")
-- 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
-- src/ui/gen2/PcMenu.lua:savePrompt() returns SaveMenu.OVERWRITE_PROMPT/
-- SAVING_PROMPT straight through to its own `lines[1]`/`lines[2]`
-- Chrome.print calls (the PC's CHANGE BOX save uses the same two prompts).
-- Indexing a plain string with [1]/[2] returns nil, not characters, so this
-- shape is a cross-file contract: it caught a real regression during review,
-- where routing these through a single Strings.source()-wrapped string (to
-- translate SaveMenu's own screen) silently turned them into non-table
-- values and left PcMenu's overwrite/saving prompt blank.
do
T.eq(type(SaveMenu.OVERWRITE_PROMPT), "table", "OVERWRITE_PROMPT stays a table for PcMenu.lua")
T.eq(SaveMenu.OVERWRITE_PROMPT[1], "There is already a", "and its first line stays indexable")
T.eq(SaveMenu.OVERWRITE_PROMPT[2], "save file. Is it", "and its second line")
T.eq(type(SaveMenu.SAVING_PROMPT), "table", "SAVING_PROMPT stays a table for PcMenu.lua")
T.eq(SaveMenu.SAVING_PROMPT[1], "SAVING… DON'T TURN", "and its first line stays indexable")
T.eq(SaveMenu.SAVING_PROMPT[2], "OFF THE POWER.", "and its second line")
end
T.finish("gen2_save_menu_translation_test")