Localization
Popii picks up the user’s Discord language setting automatically and applies translations from your locale files — no manual locale detection code required.
Setting up locale files
Section titled “Setting up locale files”Create JSON files in src/locales/ named after their IETF language tag:
src/locales/ en-US.json ← default fallback fr.json de.json pt-BR.jsonEach file is a flat key–value map:
{ "greeting": "Hello, {{username}}!", "balance": "You have {{amount}} coins.", "error.not_found": "That item doesn't exist."}{ "greeting": "Bonjour, {{username}} !", "balance": "Vous avez {{amount}} pièces.", "error.not_found": "Cet objet n'existe pas."}Translating in a handler
Section titled “Translating in a handler”Use pop.t() (or pop.translate() — they’re identical):
export default command({ name: "balance", description: "Check your coins", async do(pop) { const coins = await getBalance(pop.user.id); await pop.reply(pop.t("balance", { amount: coins })); },});{{amount}} in the string is replaced with the value from the second argument. You can pass any number of tokens.
How locale is detected
Section titled “How locale is detected”pop.locale is set from interaction.locale — the language the user has selected in Discord’s settings. Popii uses this to pick the right file automatically:
pop.locale // "en-US", "fr", "de", "pt-BR", etc.If no file exists for the user’s locale, Popii falls back to the locales.default setting (defaults to "en-US"). If the key is also missing from the fallback file, the key itself is returned unchanged.
Configuring locales
Section titled “Configuring locales”const client = popiiClient({ token: process.env.DISCORD_TOKEN!, locales: { dir: "./src/locales", // default — override if your files live elsewhere default: "en-US", // fallback locale },});Remote locale files
Section titled “Remote locale files”Fetch translations from a URL at startup instead of bundling them in your project:
locales: { url: "https://cdn.example.com/locales", // fetches /en-US.json, /fr.json, etc. default: "en-US",},Popii fetches all available locale files from the URL on startup and caches them in memory. Useful for managing translations outside your codebase.
Nesting keys
Section titled “Nesting keys”Keys are flat strings — use dots or slashes to create a naming convention for namespacing without any special config:
{ "command.ping.reply": "Pong! {{latency}}ms", "command.help.title": "Available Commands", "error.permission": "You don't have permission to do that.", "error.cooldown": "Please wait {{seconds}} seconds before using this again."}await pop.reply(pop.t("command.ping.reply", { latency: ping }));Localizing command names and descriptions
Section titled “Localizing command names and descriptions”Discord supports translated slash command names and descriptions natively. Pass nameLocalizations and descriptionLocalizations to your command:
export default command({ name: "balance", description: "Check your coins", nameLocalizations: { fr: "solde", de: "guthaben", }, descriptionLocalizations: { fr: "Vérifiez vos pièces", de: "Überprüfe deine Münzen", }, async do(pop) { /* ... */ },});Discord will show the localized name to users in the matching locale.
TypeScript — typing pop.t()
Section titled “TypeScript — typing pop.t()”pop.t() accepts any string as the key. If you want autocompletion and type-checking for your specific keys, generate a type from your locale file:
import en from "./locales/en-US.json";
declare module "popii-framework" { interface PopiiLocales { keys: keyof typeof en; }}Using pop.locale directly
Section titled “Using pop.locale directly”Access the raw locale string when you need to format numbers, dates, or currencies for the user’s region:
async do(pop) { const amount = 1_234_567.89; const formatted = new Intl.NumberFormat(pop.locale).format(amount); await pop.reply(`Amount: ${formatted}`); // "1,234,567.89" for en-US, "1 234 567,89" for fr},Loading translations in event handlers
Section titled “Loading translations in event handlers”Event handlers receive EventPop, which has the same pop.t() and pop.locale as command handlers:
import { event } from "popii-framework";
export default event(async (pop, member) => { const welcome = pop.t("greeting", { username: member.user.username }); await member.guild.systemChannel?.send(welcome);});