Spraxium logoSpraxium

Fundamentos de i18n

O módulo de i18n transforma um bot de idioma único em um bot que responde no idioma de cada usuário. Aprenda a configurar arquivos de locale, injetar o serviço de tradução e persistir preferências de usuário.

Por que um sistema estruturado de i18n importa

A maioria dos bots começa com strings em inglês hardcoded, espalhadas pelos arquivos de handler, e isso funciona bem nos primeiros centenas de usuários. O problema aparece quando a comunidade cresce e passa a incluir guilds do Brasil, da Espanha, do Japão ou da Coreia: o único caminho para dar suporte a essas pessoas passa a ser rastrear manualmente cada string no codebase e envolver tudo em lógica condicional. Essa abordagem não escala, quebra em refactors e mistura preocupações de apresentação diretamente na lógica dos comandos.

O módulo de i18n do Spraxium resolve isso com um registro centralizado de strings traduzidas, carregado uma vez no boot, um pipeline de resolução que escolhe automaticamente o locale certo para cada interação e helpers que conectam localizações de slash commands diretamente pelo decorator do comando. Escrever um bot internacionalizado fica quase igual a escrever um bot monolíngue, porque a complexidade é absorvida pelo framework, em vez de ficar espalhada pelos handlers.

Localização não é só substituição de texto. Também envolve respeitar o fato de que usuários têm um idioma preferido, que o próprio Discord tem um sistema de locale que afeta como os comandos aparecem na UI e que regras de pluralização variam bastante entre idiomas. O módulo cuida dessas três preocupações em um sistema coeso, então você quase nunca precisa pensar na mecânica enquanto escreve handlers.

Como a resolução de locale funciona

Quando um handler chama tUser, o serviço resolve o locale daquele usuário por meio de uma cadeia de prioridade em três níveis: uma preferência salva do usuário vence uma preferência salva da guild, e uma preferência salva da guild vence o locale padrão configurado. Essa ordem é o modelo correto para bots do Discord porque respeita primeiro a escolha individual, depois a configuração em nível de comunidade e só cai no padrão global do bot quando nenhuma preferência foi definida.

As preferências de usuário e guild ficam armazenadas no backend ativo de I18nStore e sobrevivem a reinicializações do bot, desde que o backend seja persistente. O MemoryStore embutido funciona bem para desenvolvimento local e para bots que não precisam persistir preferências, enquanto bots de produção devem usar uma store baseada em banco, como as mostradas na página da API da Store.

As localizações de slash commands seguem um caminho diferente: quando o bot registra comandos pela REST API do Discord, nomes e descrições podem ter variantes por locale, que o Discord renderiza nativamente no seletor de comandos. O módulo de i18n conecta essas localizações automaticamente durante a sequência de boot, então basta declarar um campo i18n em um decorator de slash para produzir uma entrada de comando totalmente localizada na UI do Discord.

Configurando o 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' },
  ],
});

Adicione o objeto de configuração ao array plugins na sua configuração do Spraxium. O módulo é autocontido e registra seu próprio I18nLifecycle, que roda automaticamente no boot antes que qualquer slash command seja registrado.

CampoTipoFinalidade
defaultLocalestring

Locale de fallback usado quando nenhuma preferência de usuário ou guild foi definida

referencesArrayPonteiros nomeados para diretórios de locale no disco
pluralRulesRecord

Funções de plural opcionais por locale, sobrescrevendo Intl.PluralRules

A API do I18nService

Injete I18nService em qualquer handler ou provider. Ele expõe métodos síncronos e assíncronos de tradução para cobrir todos os padrões de uso de que seu bot precisa.

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étodoAsyncO que faz
t(key, locale, vars?)NãoTraduz uma chave para um locale explícito com substituição opcional de variáveis
tp(key, locale, count, vars?)NãoTraduz com pluralização para um locale explícito
tUser(userId, key, vars?, guildId?)SimTraduz depois de resolver o locale pela cadeia de preferência de usuário e guild
tpUser(userId, key, count, vars?, guildId?)SimTraduz com pluralização depois de resolver o locale pela cadeia de preferências
setUserLocale(userId, locale)SimPersiste uma preferência de locale para um usuário
setGuildLocale(guildId, locale)SimPersiste uma preferência de locale para uma guild
getUserLocale(userId)SimRecupera o locale salvo do usuário ou o locale padrão
getGuildLocale(guildId)SimRecupera o locale salvo da guild ou o locale padrão
resolveLocale(userId, guildId?)SimExecuta a cadeia completa de resolução e retorna o locale vencedor