You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
tournament name, cup names/descriptions, event subtype labels
tournament UI
diva_prizes
prize names
Diva Defense rewards
These are served to every client in whatever language the data was seeded in (typically Japanese or English depending on the seed file), regardless of the client's Session.Lang().
Proposal
Two options, pick one per table based on how much text each row has:
Option A — sibling column pattern (small, 1-2 text fields)
Repo method resolves per requested language with the same fallback chain as LocalizedString.Resolve (requested → jp → en → any non-empty).
Pros: simple, no joins.
Cons: adds 3 columns per text field, doesn't scale past ~4 languages.
Option B — translation table pattern (multiple text fields, dynamic language set)
CREATETABLEshop_translations (
shop_id INTNOT NULLREFERENCES shops(id) ON DELETE CASCADE,
lang TEXTNOT NULL,
name TEXT,
description TEXT,
PRIMARY KEY (shop_id, lang)
);
Pros: scales to any number of languages without schema churn, single row change per language.
Cons: one extra query (or join) per fetch; slightly more complex seed files.
Shared helper
Regardless of the column layout, add a generic repo helper:
// resolveLocalized picks the best value for the requested language from a// map of language → text, using the same fallback chain as// LocalizedString.Resolve.funcresolveLocalized(valuesmap[string]string, langstring) string { ... }
This should mirror LocalizedString.Resolve exactly so the fallback semantics are identical whether the source is JSON or SQL.
Scope of this issue
Don't try to localize every table at once. Suggest starting with the highest-impact player-facing surfaces:
Shop names (most visible, most frequently seen)
Gacha banner + item names (existing bug reports Gacha error #175 mention players not understanding gacha content)
Event titles (campaign tent is prominently featured)
Then in subsequent PRs:
Achievement names/descriptions
Tournament cup/sub-event labels
Diva prize names
Campaign reward descriptions
Each table can be a self-contained PR sharing the resolver helper.
Non-goals
Runtime translation (e.g. calling an LLM to translate at request time). Content translations must be authored once and stored, same as quest/scenario JSON.
Changing the wire protocol. Clients still receive a single Shift-JIS string; the server just picks the right one per session. Same encoding constraints as Server-side multiple language support #188 apply (ASCII + kana + CJK on the wire).
World-wide broadcasts (e.g. BroadcastRaviente). These have no session context and stay on the server default.
Background
#188 landed full server-side multi-language support for content served from JSON files:
users.language,!langcommand,Session.Lang()/I18n())title,description, 6 other text fields) with per-session compilation and(questID, lang)-keyed cachestrings, inlinetext)s.server.i18n.*call sites in chat/guild/cafe/timer handlersGap
Content stored in the database still has a single-language column:
shops/shop_itemsevents/event_schedulegacha/gacha_items/gacha_entriescampaigns/campaign_rewardsachievementstournaments/tournament_cups/tournament_sub_eventsdiva_prizesThese are served to every client in whatever language the data was seeded in (typically Japanese or English depending on the seed file), regardless of the client's
Session.Lang().Proposal
Two options, pick one per table based on how much text each row has:
Option A — sibling column pattern (small, 1-2 text fields)
Repo method resolves per requested language with the same fallback chain as
LocalizedString.Resolve(requested → jp → en → any non-empty).Pros: simple, no joins.
Cons: adds 3 columns per text field, doesn't scale past ~4 languages.
Option B — translation table pattern (multiple text fields, dynamic language set)
Pros: scales to any number of languages without schema churn, single row change per language.
Cons: one extra query (or join) per fetch; slightly more complex seed files.
Shared helper
Regardless of the column layout, add a generic repo helper:
This should mirror
LocalizedString.Resolveexactly so the fallback semantics are identical whether the source is JSON or SQL.Scope of this issue
Don't try to localize every table at once. Suggest starting with the highest-impact player-facing surfaces:
Then in subsequent PRs:
Each table can be a self-contained PR sharing the resolver helper.
Non-goals
BroadcastRaviente). These have no session context and stay on the server default.Related