Skip to content

feat: add hybrid localisation support with per-player locale resolution#1062

Merged
confuser merged 1 commit intomasterfrom
feat/i18n
Apr 4, 2026
Merged

feat: add hybrid localisation support with per-player locale resolution#1062
confuser merged 1 commit intomasterfrom
feat/i18n

Conversation

@confuser
Copy link
Copy Markdown
Member

@confuser confuser commented Apr 4, 2026

Summary

Adds multi-language support to BanManager using a hybrid localisation model — a server-wide default locale with optional per-player overrides (similar to LuckPerms & EssentialsX).

Core changes

  • MessageRegistry: New central registry with immutable Snapshot for thread-safe atomic swaps during hot-reloads. Supports cascading locale fallback (e.g. zh_twzh → default)
  • Message refactoring: Deferred resolution — token replacements stored and applied at resolve time with locale-aware lookups via resolve(locale) / resolveFor(player)
  • Messages directory: Migrated from single messages.yml to messages/ directory with per-locale files (messages_en.yml, etc.). Legacy messages.yml retained as a backward-compatible overlay
  • Configuration: New locale.default and locale.perPlayer settings in config.yml
  • Player locale persistence: New locale column (VARCHAR(16)) in bm_players table via V2 database migration, gated by locale.perPlayer config
  • Locale-aware APIs: New kick(Message) on CommonPlayer and broadcast(Message, String) on CommonServer for per-player locale resolution
  • API migration: All 17 kick() and 26 broadcast() call sites migrated to the new Message-based overloads
  • Platform getLocale(): Implemented across Bukkit, Bungee, Velocity, Sponge (API7 + API8), and Fabric (with pre-1.21 fallback)
  • DateUtils: Locale-aware overloads for localised time formatting
  • Startup diagnostics: Logs loaded locales and missing key counts on startup

Testing

  • Unit tests: MessageRegistryTest, MessageDeferredTest, LocaleNormalisationTest, PlayerStorageTest (locale persistence), MigrationIntegrationTest (V2 schema)
  • Updated tests: KickCommandTest, KickAllCommandTest, LoglessKickAllCommandTest, NoteListenerTest, MessagesConfigTest, ConfigReloadTest, CommonJoinListenerTest
  • E2E: New locale-smoke.test.ts with German locale fixture, reload resilience check

Test plan

  • All common module unit tests pass (0 failures)
  • Clean rebase onto latest master
  • E2E tests pass on CI
  • Manual test: verify default English messages display correctly
  • Manual test: add a second locale file, confirm per-player resolution
  • Manual test: verify bmreload preserves messages and picks up new locale files
  • Manual test: verify join-deny messages use stored player locale

Adds multi-language support using a server-wide default locale with
optional per-player override (similar to LuckPerms/EssentialsX).

- Introduce MessageRegistry with immutable Snapshot for thread-safe
  atomic swaps during hot-reloads
- Refactor Message class to use deferred resolution, applying token
  replacements at resolve time with locale-aware lookups
- Add locale cascading fallback (e.g. zh_tw -> zh -> default)
- Migrate messages from single messages.yml to messages/ directory
  with per-locale files (messages_en.yml, etc.), retaining messages.yml
  as a backward-compatible overlay
- Add locale config (locale.default, locale.perPlayer) to config.yml
- Persist player locale in bm_players table (VARCHAR(16)) via V2
  database migration, gated by perPlayerLocale config
- Add locale-aware kick(Message) and broadcast(Message, String) APIs
  to CommonPlayer and CommonServer
- Migrate all kick() and broadcast() call sites (17 kick, 26 broadcast)
  to the new Message-based overloads
- Implement platform-specific getLocale() across Bukkit, Bungee,
  Velocity, Sponge (API7+API8), and Fabric (with pre-1.21 fallback)
- Add locale-aware DateUtils overloads for localised time formatting
- Log loaded locales and missing key diagnostics on startup
- Add comprehensive tests: MessageRegistry, Message deferred resolution,
  locale normalisation, player storage locale persistence, DB migration
- Add E2E locale smoke test with German locale fixture
- Update E2E sync-configs.sh to distribute messages/ directories
@confuser confuser merged commit da673fc into master Apr 4, 2026
18 checks passed
@confuser confuser deleted the feat/i18n branch April 4, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant