Skip to main content

npm

Source

Coming from @wolfstar/http-framework-i18n? Follow the migration guide.
WolfStar

@wolfstar/plugin-i18next

i18next-powered internationalization for HTTP interactions.version downloads license

Description

Plugin for @wolfstar/http-framework that adds an internationalization layer powered by i18next and @wolfstar/i18next-backend. It merges two upstream implementations:
  • @wolfstar/http-framework-i18n (now removed) — the HTTP-interaction helpers (getSupportedLanguageT, applyLocalizedBuilder, localized builders).
  • @sapphire/plugin-i18next — the plugin architecture (container.i18n, InternationalizationHandler, custom fetchLanguage, formatters, HMR).
Interactions arrive over HTTP as raw payloads, so every helper operates on discord-api-types structures instead of discord.js class instances.

Installation

Requires Node.js >=22.22.0.

Usage

Import the register entrypoint before creating the client:
The plugin registers three hooks:

Activating with modules

On framework 6.1 and later, list the ./module entry in modules in stars.config instead of importing register. It needs the optional @wolfstar/kit peer dependency, at ^0.2.0 || ^0.3.0.
The module reads its options from the i18n key of stars.config. The [specifier, options] tuple of modules still works. Its options are merged over the i18n key, so they win. The optional @wolfstar/schema peer dependency types the i18n key in defineConfig. TypeScript only loads that type once the module is imported, so import @wolfstar/plugin-i18next/module in stars.config.ts. Module options must be JSON-serialisable. Set fetchLanguage and functions in hmr.options through ClientOptions.i18n, which is shallowly merged over the module options. Because the merge is shallow, an hmr object set there replaces the module’s whole hmr object, so include enabled: true in it. Without the stars CLI, pass the ./plugin definePlugin factory to the plugins client option:
Use only one of the module, the ./plugin factory, and register. Combining them installs the plugin’s hooks twice.

Languages directory

Every top-level directory is a language, every nested .json file is a namespace (commands/ping).

Typed keys

Keys are typed through i18next’s own CustomTypeOptions augmentation. Generate it from the locale files with @wolfstar/i18next-type-generator, listed as an optional peer dependency — nothing at runtime depends on it, only the generated types consumers opt into:
The generator only emits CustomTypeOptions.resources. If defaultNS is set to anything other than i18next’s own 'translation' default — this plugin’s defaultLanguageDirectory layout usually implies 'default' — declare it separately, or every helper’s default namespace stops satisfying i18next’s Namespace type:
See i18next’s TypeScript guide for the other options the augmentation accepts. Without any of this every helper still accepts plain strings, only untyped.

Consumption

getSupported*T helpers are synchronous and only read the locales carried by the interaction. Use the asynchronous fetch* helpers when the language comes from somewhere else — they go through container.i18n.fetchLanguage:

Targets

Every helper accepts any of four raw payloads, told apart structurally — the framework never has discord.js class instances to check with instanceof: APIInteraction, APIMessage, APIChannel and APIGuild all satisfy the matching target, so payloads can be passed straight through:
Channels and messages carry no locale of their own, so the synchronous helpers fall back to defaultName for them. Use the asynchronous fetch* helpers instead: the hook receives the target’s guildId, channelId and userId, which is enough to look the language up.

Localizing command builders

Passing a single root key resolves <root>Name and <root>Description; passing two keys uses them verbatim.
[!IMPORTANT] Builder localization reads the loaded resources, so it may only run after the preLoad hook has initialized the handler. The plugin already guarantees this ordering for pieces loaded by Client#load.

Hot module replacement

When enabled, the languages directory is watched and container.i18n.reloadResources() runs on every addition, change or deletion — including new locale directories and new namespace files, which are registered on i18next and become usable without a restart. ignoreInitial defaults to true so the files already on disk do not each trigger a reload on startup; pass hmr.options to override it or any other chokidar option. The watcher is exposed as I18nextPlugin.watcher, so it can be closed on shutdown:

Options

Migrating from @wolfstar/http-framework-i18n

@wolfstar/http-framework-i18n has been removed and is no longer maintained. This plugin replaces it. The upstream changes are documented in wolfstar-project/stars-components#30; the short version:
getSupportedLanguageName, getSupportedUserLanguageName, getSupportedLanguageT, getSupportedUserLanguageT (which now also take the key and its options directly, like resolveKey did), supportedLanguages, isSupportedDiscordLocale, getLocalizedData, applyNameLocalizedBuilder, applyDescriptionLocalizedBuilder, applyLocalizedBuilder and createSelectMenuChoiceName keep the same names and signatures — only the module specifier changes. T, FT, resolveKey and resolveUserKey were removed upstream in wolfstar-project/stars-components#30 and are gone here too — see Migrating off T / FT / resolve* below. Other differences: @wolfstar/http-framework@^3.1.0 is now a peer dependency, i18next moves from ^22 to ^25, and the locales directory defaults to <root>/languages instead of an explicit path passed to load() — the layout itself is unchanged.

Migrating off T / FT / resolve*

The branded-key helpers were replaced by i18next’s native TypeScript support, so keys are plain strings typed by the CustomTypeOptions augmentation @wolfstar/i18next-type-generator emits.
TFunction#lng (along with lngs, ns and keyPrefix) is still declared, so code reading the language off a bound function keeps working — i18next assigns them on every function getFixedT returns but does not type them. LocalePrefixKey is no longer hardcoded to commands/<file>:<key>: it is now `${string}${LocaleSeparator}${string}`, with LocaleSeparator read from i18next’s TypeOptions["nsSeparator"].

Credits

Adapted from @wolfstar/http-framework-i18n and @sapphire/plugin-i18next (MIT).