mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-05-06 14:24:15 +02:00
feat(i18n): localized quest text and per-lang quest cache
Phase B of #188. Quest JSON title/description/text_main/text_sub_a/ text_sub_b/success_cond/fail_cond/contractor now accept either a plain string (existing behaviour) or a language-keyed object like "title": { "jp": "...", "en": "...", "fr": "..." } CompileQuestJSON takes the compiling session's language and resolves each field through a fallback chain (requested -> plain -> jp -> en -> any non-empty), so existing single-language quest JSONs keep working byte-for-byte unchanged. The quest cache is re-keyed on (questID, language) so compiled binaries for different languages never leak between sessions on a multi-language server. loadQuestBinary and loadQuestFile now pass s.Lang() into both the compiler and the cache. ParseQuestBinary emits plain-string LocalizedStrings, so the binary -> JSON -> binary round-trip still produces identical output. The new LocalizedString type lives in its own file and is reusable by phase C (scenarios, mail templates, shop text). Shift-JIS encoding still applies to the wire format, so localized values must use characters representable in Shift-JIS — ASCII, kana, CJK — which is documented on the type.
This commit is contained in:
@@ -210,15 +210,24 @@ type QuestJSON struct {
|
||||
// Quest identification
|
||||
QuestID uint16 `json:"quest_id"`
|
||||
|
||||
// Text (UTF-8; converted to Shift-JIS in binary)
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
TextMain string `json:"text_main"`
|
||||
TextSubA string `json:"text_sub_a"`
|
||||
TextSubB string `json:"text_sub_b"`
|
||||
SuccessCond string `json:"success_cond"`
|
||||
FailCond string `json:"fail_cond"`
|
||||
Contractor string `json:"contractor"`
|
||||
// Text (UTF-8; converted to Shift-JIS in binary).
|
||||
//
|
||||
// Each field accepts either a plain JSON string (single-language, treated
|
||||
// as the value for every language) or a language-keyed object:
|
||||
//
|
||||
// "title": "リオレウス"
|
||||
// "title": { "jp": "リオレウス", "en": "Rathalos", "fr": "Rathalos" }
|
||||
//
|
||||
// CompileQuestJSON resolves these based on the compiling session's
|
||||
// language preference (see #188 phase B).
|
||||
Title LocalizedString `json:"title"`
|
||||
Description LocalizedString `json:"description"`
|
||||
TextMain LocalizedString `json:"text_main"`
|
||||
TextSubA LocalizedString `json:"text_sub_a"`
|
||||
TextSubB LocalizedString `json:"text_sub_b"`
|
||||
SuccessCond LocalizedString `json:"success_cond"`
|
||||
FailCond LocalizedString `json:"fail_cond"`
|
||||
Contractor LocalizedString `json:"contractor"`
|
||||
|
||||
// General quest properties (generalQuestProperties section, 0x44–0x85)
|
||||
MonsterSizeMulti uint16 `json:"monster_size_multi"` // 100 = 100%
|
||||
@@ -421,7 +430,7 @@ func objectiveBytes(obj QuestObjectiveJSON) ([]byte, error) {
|
||||
// map sections, area mappings, area transitions,
|
||||
// map info, gathering points, area facilities,
|
||||
// some strings, gathering tables
|
||||
func CompileQuestJSON(data []byte) ([]byte, error) {
|
||||
func CompileQuestJSON(data []byte, lang string) ([]byte, error) {
|
||||
var q QuestJSON
|
||||
if err := json.Unmarshal(data, &q); err != nil {
|
||||
return nil, fmt.Errorf("parse quest JSON: %w", err)
|
||||
@@ -454,10 +463,14 @@ func CompileQuestJSON(data []byte) ([]byte, error) {
|
||||
|
||||
// ── Build Shift-JIS strings ─────────────────────────────────────────
|
||||
// Order matches QuestText struct: title, textMain, textSubA, textSubB,
|
||||
// successCond, failCond, contractor, description.
|
||||
// successCond, failCond, contractor, description. Each LocalizedString
|
||||
// is resolved against the requesting session's language — plain-string
|
||||
// JSON fields resolve to their literal value for every language.
|
||||
rawTexts := []string{
|
||||
q.Title, q.TextMain, q.TextSubA, q.TextSubB,
|
||||
q.SuccessCond, q.FailCond, q.Contractor, q.Description,
|
||||
q.Title.Resolve(lang), q.TextMain.Resolve(lang),
|
||||
q.TextSubA.Resolve(lang), q.TextSubB.Resolve(lang),
|
||||
q.SuccessCond.Resolve(lang), q.FailCond.Resolve(lang),
|
||||
q.Contractor.Resolve(lang), q.Description.Resolve(lang),
|
||||
}
|
||||
var sjisStrings [][]byte
|
||||
for _, s := range rawTexts {
|
||||
|
||||
Reference in New Issue
Block a user