Immutable
release. Only release title and notes can be modified.
[1.4.0] - 2026-05-13
🌍 Internacionalización (i18n) y generador propio de IDs
Esta versión introduce un subsistema de internacionalización opt-in para comandos que quieran responder en varios idiomas, con catálogo único, claves planas en notación de puntos y el helper this.t(...). También sustituye la dependencia nanoid (ESM-only, problemática en Jest) por un generador propio.
✨ Added
-
Subsistema de i18n (
src/i18n/)- Catálogo único en
src/i18n/locale/es.ts(fuente de verdad),en.tsypt.ts, con claves planas en notación de puntos:'ping.response.title','system.argument.parse_error','help.subcommand_groups.footer', etc. en.tsypt.tstipados comotypeof es: el compilador exige paridad total de claves; firmas de funciones inferidas automáticamente.- API única:
i18n.for(locale)devuelve la función traductorat(key, ...args)con tipos completos. Los argumentos se validan en cada call site según la firma del valor en el bundle. - Opt-in por comando:
BaseCommand.texponethis.t('clave', ...args)para comandos que quieran respuestas traducidas. Los comandos base pueden seguir con strings literales. - Cadena de precedencia per-guild: preferencia explícita del servidor (
LocaleStore) →interaction.localede Discord (normalizado) →DEFAULT_LOCALE('es'). - El locale es una configuración global del servidor: todos los miembros ven al bot en el mismo idioma. Cambiarlo requiere
ManageGuild. LocaleResolverresuelve el locale una sola vez por petición y lo deposita enctx.locale.LocaleStore(interfaz per-guild) +MemoryLocaleStorepor defecto; swappable por Redis u otro store distribuido víaLocaleRegistry.useStore(...).LocaleRegistryestático — mismo patrón queStoreRegistryyPluginRegistry.- Helper
resolveLocaleFromInteraction(interaction)para handlers estáticos de botones/selects/modales (no tienen acceso aBaseCommand/CommandContext). - Helpers
isSupportedLocale,normalizeLocalepara validar input externo (env vars, locales de Discord, etc.). - Soporte listo para
es,en,pt(añadir un idioma = un nuevo archivolocale/<loc>.ts+ entrada enSUPPORTED_LOCALES).
- Catálogo único en
-
Comando de ejemplo
/setlocale(src/commands/examples/setlocale.command.ts)- Cambia el idioma de respuesta para todo el servidor.
- Restringido con
@RequirePermissions(Permissions.ManageGuild). - Devuelve la confirmación en el nuevo idioma seleccionado.
- Aliases:
idioma,language.
-
Comando de ejemplo
/help-translated(src/commands/info/help-translated.command.ts)- Versión traducida del comando de ayuda. El
/helpnormal queda hardcoded para que el usuario elija qué enfoque conservar.
- Versión traducida del comando de ayuda. El
-
Generador propio de IDs (
src/utils/Id.ts)generateId(size?: number)— IDs de alta entropía y cola aleatoria decrypto.randomBytes.- Tamaño por defecto 10 caracteres.
🔧 Changed
BaseCommand— nuevos getters:locale: SupportedLocale(delegado enctx.locale).t: TFn, usado sólo por comandos opt-in comosetlocaleyhelp-translated.
CommandContext— nueva propiedadlocale: SupportedLocale, fijada por elCommandHandlerantes de ejecutar el comando.CommandHandler— resuelve el locale una sola vez por petición para que los comandos opt-in puedan usarthis.locale/this.t.HelpCommand— vuelve a ser el comando hardcoded base.- Comandos de ejemplo —
PingCommand, componentes y subcomandos se mantienen hardcoded para no imponer i18n en el template. Button/Select/Modal(src/core/components/) — sustituidos losnanoid(10)porgenerateId(10)del nuevo módulo@/utils/Id.
🗑 Removed
- Dependencia
nanoid— eliminada delpackage.json. La v5 se distribuye como ESM puro y rompíats-jest. Su rol queda cubierto porsrc/utils/Id.tssin nueva dependencia. LocaleStore.getUserLocale/setUserLocale/deleteUserLocale— reemplazados por sus equivalentes per-guild (getGuildLocale, etc.). El idioma del bot es una configuración del servidor, no del usuario.
📚 Documentation
- Nuevo
src/i18n/README.md— guía completa: filosofía, arquitectura, cómo añadir un mensaje, cómo añadir un idioma, cómo configurar un store distribuido, cómo eliminar i18n del bot en un único punto, API pública. README.mdprincipal — nueva sección "🌍 Internacionalización (i18n)" en Características y enlace en la navegación.- Actualizadas referencias a generación de IDs (
src/core/components/README.md,src/core/registry/component.registry.ts): ahora apuntan a@/utils/Id. README.mdprincipal — eliminada la referencia ananoidde la sección "Utilidades".
✅ Testing
- Nuevos tests en
tests/unit/i18n/:types.test.ts—SUPPORTED_LOCALES,isSupportedLocale,normalizeLocale.translator.test.ts— bundle globali18n+ funciónTFn(claves planas, args tipados, fallback, concurrencia).memory.locale.store.test.ts—MemoryLocaleStoreper-guild (incluye escrituras paralelas).locale.resolver.test.ts— cadena de precedencia per-guild completa.registry.test.ts— comportamiento estático delLocaleRegistry.
- Nuevo
tests/unit/utils/Id.test.ts— formato, validación, orden temporal, unicidad. - Tests existentes actualizados:
CommandCategories.test.ts,cooldown.plugin.test.tsypermissions.plugin.test.ts. - 212 tests pasan.
⚠️ Notas de migración
- Locale per-guild, no per-user:
LocaleStore.getUserLocale→getGuildLocale. Si tu bot ya tenía preferencias persistidas por usuario, hay que migrar las claves aguildId. - Convención de claves: planas con notación de puntos (
'<dominio>.<seccion>.<mensaje>'). No camelCase, no objetos anidados. - Consumo opt-in: usa
this.t('clave', ...args)sólo en comandos que quieras traducir. Para eliminar i18n, borrasetlocale,help-translated, el gettertdeBaseCommandy el módulosrc/i18n. - Para añadir un mensaje nuevo: editar primero
src/i18n/locale/es.ts; el compilador hará fallaren.tsypt.tshasta que se añadan las traducciones (contrato a propósito). - Si se usa
nanoiddesde código propio downstream, migrar agenerateIdde@/utils/Ido reinstalarnanoidcomo dependencia propia.