Conversation
- Add i18next dependency for internationalization - Create src/lib/i18n.ts (initI18n, addTranslations, createT) - Create src/core/core.config.ts with locale ENUM (en/fr) - Extend ConfigProvider with locale param and config.t() for key lookup - Add getConfigTypeName() with optional TFunction for localized type names
- Init i18n before module loading in src/index.ts - Add loadModuleI18n() to auto-discover module i18n/*.json files - Load core i18n explicitly in core.module.ts - Resolve locale per guild via getLocaleForGuild() in config service - Propagate locale through all config service methods
- Add core translations (en.json + fr.json) for config UI, modules list,
scalar list editor, modals, placeholders, and error messages
- Migrate all hardcoded strings in core-messages.ts to use coreConfig.t()
- Replace template literals with i18next named interpolation ({{param}})
- Update require-admin.ts to use localized messages
- Update core-messages.test.ts for new i18n-based output
- Migrate all 7 config handlers (string, number, enum, select, user, role, channel, category) to use config.t() with ns fallback - Localize scalar-list-editor with i18n keys - Update integration tests with mock for #core/core.module.js to prevent circular dependency
- Add setDescriptionLocalizations() for /config and /modules commands - Localize autocomplete module names via config.t() - Migrate all button interactions (config-page, configure-module, enable-module, disable-module, toggle-option) to use coreConfig.t() - Update interaction-create.listener.ts for localized responses
- Migrate thread-creator config name/description to English defaults, add i18n/en.json + i18n/fr.json - Migrate test-config module (name, config, descriptions) to i18n - Both modules use module-level i18n/ directory with EN/FR locale files - Module names in core i18n fallback: modules.thread-creator, modules.test-config
- Add i18n feature card to index.md - Mention i18n in getting-started feature description, project structure, and naming conventions - Add i18n init step to architecture boot sequence - Add full Internationalisation section to creating-a-module.md - Add Localisation section to commands.md, configuration.md, interactions.md, listeners.md
… bot just doesn't use it
Use the config.currentValue translation key in the config.option template so the 'Current: / Valeur actuelle :' label is shown again. Also fix duplicate import in the test file.
…reak circular deps Create src/core/utils/core-config.ts with getCoreT() and replyWithCoreT() helpers that centralize the configService.getConfigForModuleIn(coreModule) pattern. Update all config handlers and button interaction handlers to use these helpers instead of importing coreModule directly, eliminating the circular dependency chain (core.module -> handlers -> core.module) and removing the repeated 4-line boilerplate from 9 files.
- type.text: Short text/Texte court -> Text/Texte - type.boolean: Yes/No/Oui/Non -> Boolean/Booléen - type.enum -> type.choice (actual key used by getConfigTypeName) - config.toggle.enable/disable -> modules.enable/disable (actual keys) - Update getConfigTypeName example to show correct values - Fix stale config.toggle.enable references in configuration and creating-a-module guides
Stop persisting schema defaults; ConfigProvider.get() resolves them at read time, looking up `config.<key>.default` for free-text fields so an unconfigured value follows the guild locale. Enum/number/boolean/list defaults are returned verbatim. thread-creator's welcome message uses this; existing guilds keep their stored default (no migration). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show a localized `_(default)_` suffix next to any value still served by its schema default, so admins can tell what they have actually set apart from untouched defaults. Backed by ConfigProvider.isDefault(key), which the lazy-default resolution now makes possible (a stored value, including a `null` clear, counts as explicitly set). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an optional `display: "language"` hint on enum entries; formatConfigValue renders such options through Intl.DisplayNames in the viewer's locale. The core `locale` field now shows Anglais/Français (FR) or English/French (EN) — both in the /config readout and the select menu — with no translation keys to maintain. Other enums and value types are returned verbatim as before. The locale field description also drops its now-redundant "(en/fr)" suffix, since the localized option names already convey the available languages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each `display: "language"` option now shows its flag in the select menu via configValueEmoji, backed by a small curated code→flag table (en → 🇬🇧, since English has no canonical flag). Codes absent from the table get no emoji. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A "Reset…" button at the bottom of the /config panel opens an ephemeral multi-select listing the currently-overridden fields, plus an "All fields" entry; the chosen fields are cleared and fall back to their defaults. The button is disabled when nothing is overridden, so it is never a no-op. Resetting removes the stored value (rather than writing null), letting the lazy per-locale default take over again. Adds ConfigProvider.isSet, ConfigService.resetFieldsForModuleIn, and a page-aware source-panel refresh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"Core Module" / "Module Principal" was developer-facing jargon; the module a
guild admin actually opens via /config is the bot's global settings. Rename it
to "Global Configuration" (EN) / "Configuration Globale" (FR), in the schema
fallback and both locale bundles.
Also reword the config panel header to "Settings · {{moduleName}}" /
"Paramètres · {{moduleName}}", avoiding the redundant "Settings of Global
Configuration" the previous "of"-style template produced.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /config "default" marker keyed on isDefault, which was true only for an unset field that *declares* a defaultValue — so an unset list with no default (e.g. thread-creator's channels) showed "—" with no marker. Key it on !isSet instead: any field the admin hasn't set is flagged. This also makes the marker the exact complement of the reset picker (set ⟺ resettable ⟺ no marker). The now-unused isDefault is removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AntoineJT
enabled auto-merge (rebase)
June 17, 2026 20:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR adds full internationalization (i18n) support to the bot, then builds a localized configuration experience on top of it. Modules provide translation files, guild admins pick the bot language via
/config(Global configuration →Language), and the config UI is fully localized — including defaults, value labels and a reset flow.
Changes
i18n infrastructure
#lib/i18n.tswrapping i18next with namespace fallback tocorei18n/<locale>.jsonfiles are loaded at startupConfigProviderexposest()(per-guild locale) andlocalegetConfigTypeName()accepts an optionalTFunctionfor localized type namesCore translations
src/core/i18n/{en,fr}.jsonwith all core UI stringsen/fr) incore.config.tsModule translations
test-config/i18n/{en,fr}.jsonandthread-creator/i18n/{en,fr}.jsonConfig defaults (lazy + per-locale)
ConfigProvider.get()resolves them at read time, so an unset value tracks the guild locale and changes liveconfig.<key>.defaultkey (the schemadefaultValuestays the English fallback)Config UI
_(default)_(config.defaultSuffix), backed byConfigProvider.isSet()display: "language"; its options render as localized names viaIntl.DisplayNames, with flag emojis, in both the readout and the select menu (no translation keys to maintain)ConfigService.resetFieldsForModuleIn()Settings · {{module}}/Paramètres · {{module}}Refactoring
src/core/utils/core-config.ts(getCoreT,getCoreLocale,replyWithCoreT) to centralize core config access and break circular depscoreModuleDocumentation
docs/site/{en,fr}/guide/localization*.md(+ sidebar entries), covering module/field translations, localizable defaults, the default marker, thedisplay: "language"hint and the reset flowdocs/functional.mdupdated for the language behavior, default marker and resetVerification
pnpm buildpasses cleanpnpm vitest run)