Runtime internationalization
Discord localizes command names separately through nameLocalizations and descriptionLocalizations. Runtime replies use the invoking user's interaction.locale; prefix messages fall…
Configure catalogs
Catalogs are flat key/value maps. createI18n infers the union of message keys
for direct i18n.t(...) calls:
import { SpearClient, createI18n } from "spearkit";
const i18n = createI18n({
defaultLocale: "en-US",
messages: {
"en-US": {
"ping.reply": "Pong! {ms}ms",
"errors.denied": "You cannot do that.",
},
tr: {
"ping.reply": "Pong! {ms}ms",
"errors.denied": "Bunu yapamazsın.",
},
},
});
const client = new SpearClient({ i18n });Locale matching is case-insensitive and normalizes _ to -. Resolution order:
- exact locale (
tr-TR); - language catalog (
tr); - optional
fallbackLocaleand its language; defaultLocaleand its language.
Missing keys return the key itself unless missing(key, locale) is configured.
Missing {params} remain visible instead of silently disappearing.
Translate in handlers
Every interaction context, prefix context, and hybrid context exposes async
ctx.t(...):
const ping = command({
name: "ping",
description: "Check latency",
run: async (ctx) =>
ctx.reply(await ctx.t("ping.reply", { ms: ctx.client.ws.ping })),
});ctx.t is async because locale selection may read a guild/user settings store.
Use i18n.t(locale, key, params) directly when the locale is already known and
you want a synchronous result.
Per-guild or per-user locale
resolveLocale runs before Discord's locale fallback and may be async:
const i18n = createI18n({
defaultLocale: "en-US",
messages,
resolveLocale: async ({ guildId, userId, locale, guildLocale }) => {
const userChoice = await userLanguages.get(userId);
if (userChoice) return userChoice;
if (guildId) return (await guildSettings.get(guildId)).locale;
return locale ?? guildLocale;
},
});The resolver receives locale, guildLocale, guildId, and userId.
Interactions prefer the invoking user's locale by default. Prefix messages have
no interaction locale, so they use guild.preferredLocale before the configured
default.
Formatter messages
Use a function for grammar, pluralization, or custom formatting:
const i18n = createI18n({
defaultLocale: "en",
messages: {
en: {
items: ({ count }) => `${count} item${count === 1 ? "" : "s"}`,
},
},
});spearkit intentionally does not bundle i18next or an ICU parser. Applications
that need full ICU message syntax can still call their own translator from
resolveLocale/handlers.
Sources:
Environment & dotenv
spearkit includes a tiny, dependency-free .env loader and a typed reader over process.env, so a bot needs no extra dotenv dependency. The client auto-loads .env on start(), and…
Everyday helpers
Small utilities for the tasks every bot copies from Stack Overflow: invite links, autocomplete lists, mention parsing, and long replies.