Spraxium logoSpraxium

Fundamentos de i18n

El módulo i18n convierte un bot monolingüe en uno que responde en el idioma de cada usuario. Aprende a configurar archivos de locale, inyectar el servicio de traducción y persistir preferencias de usuario.

Por qué importa un sistema de i18n estructurado

La mayoría de los bots empiezan con strings en inglés hardcodeadas y dispersas en archivos de handlers, y eso funciona bien para los primeros cientos de usuarios. El problema aparece cuando la comunidad crece e incluye guilds en Brasil, España, Japón o Corea: el único camino para dar soporte a esos usuarios es rastrear manualmente cada string en todo el codebase y envolverla en lógica condicional. Ese enfoque no escala, se rompe con los refactors y mezcla preocupaciones de presentación directamente en la lógica de comandos.

El módulo i18n de Spraxium resuelve esto con un registro centralizado de strings traducidas cargado una sola vez en el arranque, un pipeline de resolución que selecciona automáticamente el locale correcto para cada interacción y helpers que conectan localizaciones de slash commands directamente desde el decorator del comando. Escribir un bot internacionalizado se parece casi igual a escribir uno monolingüe, porque la complejidad la absorbe el framework en lugar de quedar dispersa entre métodos de handlers.

La localización no trata solo de reemplazar texto. Trata de respetar que los usuarios tienen un idioma preferido, que Discord tiene su propio sistema de locale que afecta cómo se muestran los comandos en la UI, y que las reglas de pluralización varían de forma significativa entre idiomas. El módulo maneja las tres preocupaciones en un sistema cohesivo, así que rara vez tienes que pensar en la mecánica mientras escribes handlers.

Cómo funciona la resolución de locale

Cuando un handler llama a tUser, el servicio resuelve el locale de ese usuario mediante una cadena de prioridad de tres niveles: una preferencia de usuario guardada tiene prioridad sobre una preferencia de guild guardada, y una preferencia de guild guardada queda por encima del locale predeterminado configurado. Este orden es el modelo correcto para bots de Discord porque respeta primero las decisiones individuales, luego los ajustes a nivel comunidad, y cae al valor predeterminado del bot solo cuando no se ha definido ninguna preferencia.

Las preferencias de usuario y guild se almacenan en el backend activo de I18nStore y sobreviven a reinicios del bot siempre que el backend sea persistente. El MemoryStore integrado funciona para desarrollo local y bots que no necesitan preferencias persistentes, mientras que en producción conviene usar un store respaldado por base de datos como los mostrados en la página de la API del Store.

Las localizaciones de slash commands siguen un camino diferente: cuando el bot registra comandos con la REST API de Discord, los nombres y descripciones pueden tener variantes por locale que Discord renderiza de forma nativa en el selector de comandos. El módulo i18n conecta esas localizaciones automáticamente durante la secuencia de arranque, por lo que declarar un campo i18n en un decorator de slash es todo lo necesario para producir una entrada de comando completamente localizada en la UI de Discord.

Configuración del módulo

config/i18n.config.ts
import { defineI18n } from '@spraxium/i18n';

export const i18nConfig = defineI18n({
  defaultLocale: 'en-US',
  references: [
    { name: 'commands', path: './locales/commands' },
    { name: 'messages', path: './locales/messages' },
    { name: 'errors', path: './locales/errors' },
  ],
});

Agrega el objeto de configuración al array plugins de tu configuración de Spraxium. El módulo es autocontenido y registra su propio I18nLifecycle, que se ejecuta automáticamente en el arranque antes de que se registren los slash commands.

CampoTipoFinalidad
defaultLocalestring

Locale de fallback usado cuando no existe preferencia de usuario o guild

referencesArrayPunteros nombrados a directorios de locales en disco
pluralRulesRecord

Funciones de plural opcionales por locale, sobrescribiendo Intl.PluralRules

API de I18nService

Inyecta I18nService en cualquier handler o provider. Expone métodos de traducción síncronos y asíncronos para cubrir cualquier patrón de uso que necesite tu bot.

import { Ctx, SlashCommandHandler } from '@spraxium/common';
import { I18nService } from '@spraxium/i18n';
import type { ChatInputCommandInteraction } from 'discord.js';
import { PingCommand } from '../commands/ping.command';

@SlashCommandHandler(PingCommand)
export class PingHandler {
  constructor(private readonly i18n: I18nService) {}

  async handle(@Ctx() interaction: ChatInputCommandInteraction): Promise<void> {
    const locale = interaction.locale;
    const message = this.i18n.t('commands.ping.response', locale);
    await interaction.reply(message);
  }
}
MétodoAsíncronoQué hace
t(key, locale, vars?)NoTraduce una key para un locale explícito con sustitución opcional de variables
tp(key, locale, count, vars?)NoTraduce con pluralización para un locale explícito
tUser(userId, key, vars?, guildId?)SíTraduce después de resolver el locale por la cadena de preferencias de usuario y guild
tpUser(userId, key, count, vars?, guildId?)SíTraducción pluralizada después de resolver el locale por la cadena de preferencias
setUserLocale(userId, locale)SíPersiste una preferencia de locale para un usuario
setGuildLocale(guildId, locale)SíPersiste una preferencia de locale para una guild
getUserLocale(userId)SíRecupera el locale guardado del usuario o el predeterminado
getGuildLocale(guildId)SíRecupera el locale guardado de la guild o el predeterminado
resolveLocale(userId, guildId?)SíEjecuta la cadena completa de resolución y devuelve el locale ganador