Files
gen1recomp/tests/engine/game3_easy_chat_text_test.lua
T
thibautbus bfc2534a81 Let a translation reach the Easy Chat vocabulary
The Easy Chat screen picks its words from src/core/game3/easy_chat_data.lua, the ROM's own word tables as the extractor writes them: 1,813 English words across 22 groups, plus the group names the picker lists. It drew them straight from that table, so every word the player composes with stayed English, and the screen's own prompts -- routed through Strings() already -- sat in a frame of untranslated vocabulary.

src/core/game3/easy_chat_text.lua is the seam. The data module stays exactly what the extractor produced, because it is regenerated from the ROM and anything written into it would be lost on the next import; the words are translated where they are drawn instead, the same split the rest of game3 uses for extracted text. Five sites go through it: the group list, its header, the word list, the word sitting in a slot, and the saved profile a message box prints (ShowEasyChatMessage). The ids the save file stores are untouched.

Each word carries its group's context ("easyChat.FEELINGS"), because the same English word means different things in different groups and 155 of them collide with labels this engine already translates elsewhere (ATTACK, BAG, CANCEL, BACK...). Strings() falls back to the plain key, so a catalog that does not need the distinction lands with one entry -- with the consequence that a mod which already translates BAG for the menus now sees that wording in the Easy Chat list too, until it adds a context-specific entry.

A seam nothing can discover is not much use: tools/modkit.py's catalog generator emits these keys too. The literal harvester cannot see them (the picker looks each word up at draw time, not at a Strings("...") call site), so the dataset dump walks EasyChatData.GROUPS and writes 1,835 rows into lang/strings.lua, where a translator finds them beside the rest of the engine text.

Each word is clipped to the box it sits in: in the picker, the red selection rectangle; in the phrase frame, the slot's own frame. The cart's words fit those boxes (72 px at most); a longer translation is cut at the box edge instead of running past the highlight, into the next slot's cursor, or off screen. Group names get the room up to the scroll arrows instead, because an official translation already needs more than the rectangle: the French cart's VIE QUOTIDIEN. is 83 px.

tests/engine/game3_easy_chat_text_test.lua covers the context lookup, the fallback to a plain key, the untouched English with no catalog, and that the extracted data itself is never rewritten. It lives in tests/engine so the T1/T2 tier actually runs it.
2026-09-21 17:24:41 +02:00

62 lines
2.7 KiB
Lua

#!/usr/bin/env luajit
-- The Easy Chat vocabulary is engine data extracted from the ROM, so it stays
-- English in easy_chat_data.lua and is translated where it is drawn
-- (src/core/game3/easy_chat_text.lua). Each word carries its group's context,
-- because the same English word means different things in different groups --
-- and collides with menu labels this engine already translates elsewhere.
package.path = "./?.lua;./?/init.lua;" .. package.path
love = require("tests.love_stub")
local T = require("tests.harness")
local check = T.check
local Strings = require("src.core.Strings")
local EasyChatData = require("src.core.game3.easy_chat_data")
local EasyChatText = require("src.core.game3.easy_chat_text")
local function groupNamed(name)
for id, group in pairs(EasyChatData.GROUPS) do
if group.name == name then return id, group end
end
end
local moveId, moveGroup = groupNamed("MOVE 1")
local feelId, feelGroup = groupNamed("FEELINGS")
check(moveGroup and feelGroup, "the extracted data still has the MOVE 1 and FEELINGS groups")
local moveWord = moveGroup.words[1]
local feelWord = feelGroup.words[1]
-- ------------------------------------------------------ no catalog loaded
Strings.load({})
check(EasyChatText.word(moveWord.id) == moveWord.text, "a word is its English self with no catalog")
check(EasyChatText.groupName(feelGroup) == "FEELINGS", "so is a group name")
check(EasyChatData.getWord(moveWord.id) == moveWord.text,
"and the extracted data itself is never rewritten")
-- ------------------------------------------------------- a mod's catalog
Strings.load({ strings = {
["easyChat.group|FEELINGS"] = "SENTIMENTS",
["easyChat.MOVE 1|" .. moveWord.text] = "ATTAQUE-FR",
[feelWord.text] = "SANS-CONTEXTE",
} })
check(EasyChatText.groupName(feelGroup) == "SENTIMENTS", "a group name translates under its own context")
check(EasyChatText.word(moveWord.id) == "ATTAQUE-FR", "a word translates under its group's context")
check(EasyChatText.wordInGroup(moveWord, moveGroup) == "ATTAQUE-FR",
"the picker's own list resolves the same key without decoding the id")
check(EasyChatText.word(feelWord.id) == "SANS-CONTEXTE",
"a catalog that does not care about the group still lands through the plain key")
local phrase = EasyChatText.phrase({ moveWord.id, feelWord.id }, 2, 1)
check(phrase == "ATTAQUE-FR SANS-CONTEXTE", "a saved profile prints its words translated")
check(EasyChatText.phrase({}, 2, 2) == "\n", "an empty profile still yields its rows")
-- A word id that is not in the table keeps the data module's own placeholder.
check(EasyChatText.word(EasyChatData.EC_WORD_UNDEFINED) == "", "an unset slot stays empty")
Strings.load({})
T.finish("game3_easy_chat_text_test")