Phase A plumbing for #188. Adds a users.language column (migration
0022), UserRepo.GetLanguage/SetLanguage, and Session.Lang()/SetLang
accessors so future phases can resolve localized content per session
instead of falling back to the server-wide config.Language.
The preference is loaded from the DB on login and persisted via a new
!lang <en|jp|fr|es> chat command that shows the current language when
called without an argument, validates the code (case-insensitive), and
replies in the newly selected language so the switch is visible
immediately. An empty stored value falls back to config.Language.
sys_language.go exposes getLangStringsFor(code) as the new dispatch
primitive; getLangStrings(server) is now a thin wrapper so existing
callers keep working unchanged. isSupportedLang + supportedLangs keep
the !lang validator in sync with the dispatcher.
Localized quest/scenario content and per-session i18n lookups in
existing handlers are deliberately out of scope for phase A — this
commit ships only the plumbing so it can be reviewed and deployed
independently.
Add 30 new tests to handlers_commands_test.go covering previously
untested paths: raviente with semaphore (start, multiplier, ZZ-only
sed/res commands, version gating), course enable/disable/locked/alias,
reload with other players and objects, help filtering for non-op vs op,
ban error paths and long-form duration aliases, and disabled-command
gating for all 12 commands. Total: 62 tests, all passing with -race.
Cover parseChatCommand with 31 tests across all command types:
timer toggle, PSN set/exists, rights, discord token, playtime,
help, ban (permanent/timed/non-op/invalid CID/duration units),
teleport, key quest (version check/get/set), raviente, and
course. Includes mockUserRepoCommands for fine-grained tracking
of command side effects.