mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 15:01:11 +02:00
Merge pull request #1759 from thibautbus/fix/translate-gold-title-and-save-menus
Translate Gold's title menu and SAVE screen
This commit is contained in:
+11
-10
@@ -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
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ local Font = require("src.render.Font")
|
|||||||
local Palettes = require("src.world.gen2.Palettes")
|
local Palettes = require("src.world.gen2.Palettes")
|
||||||
local Phone = require("src.core.gen2.Phone")
|
local Phone = require("src.core.gen2.Phone")
|
||||||
local SpriteRenderer = require("src.render.SpriteRenderer")
|
local SpriteRenderer = require("src.render.SpriteRenderer")
|
||||||
|
local Strings = require("src.core.Strings")
|
||||||
local TileSheet = require("src.ui.gen2.TileSheet")
|
local TileSheet = require("src.ui.gen2.TileSheet")
|
||||||
|
|
||||||
local Pokegear = {}
|
local Pokegear = {}
|
||||||
@@ -1881,7 +1882,7 @@ function Pokegear:drawClock()
|
|||||||
self:text(Chrome.number(display, 2), 6, 8)
|
self:text(Chrome.number(display, 2), 6, 8)
|
||||||
self:text(":", 8, 8)
|
self:text(":", 8, 8)
|
||||||
self:text(Chrome.number(minute, 2, true), 9, 8)
|
self:text(Chrome.number(minute, 2, true), 9, 8)
|
||||||
self:text(hour < 12 and "AM" or "PM", 12, 8)
|
self:text(Strings(hour < 12 and "AM" or "PM"), 12, 8)
|
||||||
|
|
||||||
-- The bottom Textbox is part of the card (lb bc, 4, 18 at (0,12)), and
|
-- The bottom Textbox is part of the card (lb bc, 4, 18 at (0,12)), and
|
||||||
-- PokegearClock_Init prints PokegearPressButtonText straight into it
|
-- PokegearClock_Init prints PokegearPressButtonText straight into it
|
||||||
@@ -2218,7 +2219,7 @@ function Pokegear:drawPlain()
|
|||||||
if display == 0 then display = 12 end
|
if display == 0 then display = 12 end
|
||||||
Chrome.print(("%s:%s %s"):format(
|
Chrome.print(("%s:%s %s"):format(
|
||||||
Chrome.number(display, 2), Chrome.number(minute, 2, true),
|
Chrome.number(display, 2), Chrome.number(minute, 2, true),
|
||||||
hour < 12 and "AM" or "PM"), 5, 9)
|
Strings(hour < 12 and "AM" or "PM")), 5, 9)
|
||||||
Chrome.print(Clock.daytimeLabel(hour), 5, 11)
|
Chrome.print(Clock.daytimeLabel(hour), 5, 11)
|
||||||
elseif id == "radio" then
|
elseif id == "radio" then
|
||||||
-- Without the gear sheet there is no dial art, so the frequencies go down
|
-- Without the gear sheet there is no dial art, so the frequencies go down
|
||||||
|
|||||||
+54
-12
@@ -24,8 +24,10 @@
|
|||||||
-- one-line call.
|
-- one-line call.
|
||||||
|
|
||||||
local Chrome = require("src.ui.gen2.Chrome")
|
local Chrome = require("src.ui.gen2.Chrome")
|
||||||
|
local Logger = require("src.core.Logger")
|
||||||
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 +57,50 @@ 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() reads these two tables' lines[1]/
|
||||||
|
-- lines[2] directly, so their shape is a cross-file contract: keep them
|
||||||
|
-- plain, untranslated tables).
|
||||||
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." }
|
||||||
|
|
||||||
|
-- Translatable copies of the two prompts above, one \n-joined key each, used
|
||||||
|
-- only by this screen's own prompt() below. One key per prompt lets a
|
||||||
|
-- translation write one whole, freely reordered sentence instead of two
|
||||||
|
-- fragments translated in isolation, and lets a cart whose own text is a
|
||||||
|
-- single line (German's SAVING prompt) say so directly by simply omitting
|
||||||
|
-- the "\n" -- the per-line override style used elsewhere requires a
|
||||||
|
-- non-empty value for every line, so it can't express "this line is blank".
|
||||||
|
--
|
||||||
|
-- Written as literals, not `table.concat(OVERWRITE_PROMPT, "\n")`: the
|
||||||
|
-- translation tooling's string harvester only recognizes a literal inside
|
||||||
|
-- Strings.source(...), not a computed expression, so a concat call here
|
||||||
|
-- would quietly never reach a translator. Keep 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 translated "line one\nline two" string back into the two-slot
|
||||||
|
-- table drawPanel's fixed Chrome.print calls expect. No "\n" at all (a
|
||||||
|
-- single-line message, or German's one-line SAVING prompt) lands whole on
|
||||||
|
-- the first slot, matching the untranslated code's own { text, "" } shape.
|
||||||
|
--
|
||||||
|
-- Only the first "\n" splits, since this box has room for exactly two
|
||||||
|
-- lines. A third line would otherwise draw as a raw newline byte -- garbage
|
||||||
|
-- glyph data -- with no other sign anything went wrong, so this warns once
|
||||||
|
-- per string instead.
|
||||||
|
local warnedTooManyLines = {}
|
||||||
|
local function twoLines(text)
|
||||||
|
local first, second = text:match("^(.-)\n(.*)$")
|
||||||
|
if second and second:find("\n", 1, true) and not warnedTooManyLines[text] then
|
||||||
|
warnedTooManyLines[text] = true
|
||||||
|
Logger.warn("SaveMenu: translation of %q has more than two lines; " ..
|
||||||
|
"only the first two fit this box", text)
|
||||||
|
end
|
||||||
|
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 +213,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 +232,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 +253,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,199 @@
|
|||||||
|
-- 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
|
||||||
|
|
||||||
|
-- A translation with a THIRD line (a second embedded "\n") has nowhere on
|
||||||
|
-- screen to go -- drawPanel's box has room for exactly two Chrome.print
|
||||||
|
-- calls -- so it must not silently draw the literal newline byte as glyph
|
||||||
|
-- garbage on the second line, and should warn so a translator notices.
|
||||||
|
do
|
||||||
|
Strings.load({
|
||||||
|
strings = {
|
||||||
|
["Would you like to\nsave the game?"] = "Ligne un\nLigne deux\nLigne trois",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local warned = {}
|
||||||
|
require("src.core.Logger").warn = function(fmt, ...)
|
||||||
|
warned[#warned + 1] = select("#", ...) > 0 and fmt:format(...) or fmt
|
||||||
|
end
|
||||||
|
|
||||||
|
local menu = SaveMenu.new({}, { save = SAVE, existed = false })
|
||||||
|
drawn = {}
|
||||||
|
menu:drawPanel()
|
||||||
|
T.eq(drawnAt(PROMPT1_X, PROMPT1_Y), "Ligne un", "only the first line reaches the box")
|
||||||
|
T.eq(drawnAt(PROMPT2_X, PROMPT2_Y), "Ligne deux\nLigne trois",
|
||||||
|
"the rest lands in the second slot rather than vanishing")
|
||||||
|
T.check(#warned == 1, "and a single warning is logged")
|
||||||
|
|
||||||
|
drawn = {}
|
||||||
|
menu:drawPanel()
|
||||||
|
T.check(#warned == 1, "the warning does not repeat for the same text")
|
||||||
|
|
||||||
|
require("src.core.Logger").warn = function() end
|
||||||
|
Strings.load({})
|
||||||
|
end
|
||||||
|
|
||||||
|
T.finish("gen2_save_menu_translation_test")
|
||||||
Reference in New Issue
Block a user