Soportando diferentes lenguajes
Seyfert tiene una función i18n incorporada que te permite crear archivos de idioma y utilizarlos en tu bot.
Actualizando seyfert config
Antes de empezar este capítulo debe actualizar seyfert.config.mjs
para decirle a seyfert donde estará nuestro fichero de idiomas.
// @ts-checkimport { const config: { bot(data: RuntimeConfig): InternalRuntimeConfig; http(data: RuntimeConfigHTTP): InternalRuntimeConfigHTTP;}
config } from "seyfert";
export default const config: { bot(data: RuntimeConfig): InternalRuntimeConfig; http(data: RuntimeConfigHTTP): InternalRuntimeConfigHTTP;}
config.function bot(data: RuntimeConfig): InternalRuntimeConfig
Configurations for the bot.
bot({ token: string
token: var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The process.env
property returns an object containing the user environment.
See environ(7)
.
An example of this object looks like:
{ TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node'}
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other Worker
threads.
In other words, the following example would not work:
Terminal window node -e 'process.env.foo = "bar"' && echo $foo
While the following will:
import { env } from 'node:process';
env.foo = 'bar';console.log(env.foo);
Assigning a property on process.env
will implicitly convert the value
to a string. This behavior is deprecated. Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
import { env } from 'node:process';
env.test = null;console.log(env.test);// => 'null'env.test = undefined;console.log(env.test);// => 'undefined'
Use delete
to delete a property from process.env
.
import { env } from 'node:process';
env.TEST = 1;delete env.TEST;console.log(env.TEST);// => undefined
On Windows operating systems, environment variables are case-insensitive.
import { env } from 'node:process';
env.TEST = 1;console.log(env.test);// => 1
Unless explicitly specified when creating a Worker
instance,
each Worker
thread has its own copy of process.env
, based on its
parent thread's process.env
, or whatever was specified as the env
option
to the Worker
constructor. Changes to process.env
will not be visible
across Worker
threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of process.env
on a Worker
instance operates in a case-sensitive manner
unlike the main thread.
env.string | undefined
BOT_TOKEN ?? "", intents?: number | IntentStrings | number[]
intents: ["Guilds"], locations: RCLocations
locations: { RCLocations.base: string
base: "dist", RCLocations.commands?: string
commands: "commands", RCLocations.events?: string
events: "events", RCLocations.langs?: string
langs: "languages" // - src/languages será nuestro directorio de idiomas }});
Creación de un archivo de traducciones
Cada fichero de idioma exportará por defecto un objeto que contendrá las traducciones del idioma.
export default { hello: string
hello: "Each key value pair will be the translation for the key", foo: { bar: string; baz: () => string; ping: ({ ping }: { ping: number; }) => string;}
foo: { bar: string
bar: "You may nest objects to create a more complex language file", baz: () => string
baz: () => `You may also use functions to pass variables to the translation and add some logic`, ping: ({ ping }: { ping: number;}) => string
ping: ({ ping: number
ping }: { ping: number
ping: number }) => `The ping is ${ping: number
ping}` }, qux: string
qux: [ "You may also use arrays to create a list of translations", "This is the second item in the list" ].Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
join("\n")}
Puedes crear tantos archivos de idioma como quieras, seyfert los cargará y estarán disponibles para usar en tu bot.
import type English from "./en";
export default { hello: "Hola, mundo!", foo: { bar: "Puedes anidar objetos para crear un archivo de idioma más complejo", baz: () => `Puedes usar funciones para pasar variables a la traducción y agregar lógica`, ping: ({ ping }) => `El ping es ${ping}` }, qux: [ "También puedes usar arrays para crear una lista de traducciones", "Este es el segundo elemento de la lista" ].join("\n")} satisfies typeof English; // Este es un type de utilidad para asegurar que el objeto es el mismo en todos los idiomas
NA continuación debemos hacer algunas actualizaciones al declare module
en el archivo index:
import type English from './languages/en';import { Client, type ParseClient, type ParseLocales } from "seyfert";
const client = new Client();
client.start();
declare module 'seyfert' { interface UsingClient extends ParseClient<Client<true>> { } // interface UsingClient extends ParseClient<HttpClient> { } // Si utiliza la API Rest
interface DefaultLocale extends ParseLocales<typeof English> { }}
Una vez hecho esto, podrá utilizar el idioma en sus comandos, eventos, componentes, etc.
Usando traducciones en tus comandos
Veamos un ejemplo con nuestro comando ping
añadiendo una opción para responder en un idioma específico
import { class Command
Command, function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare, function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { options: SubCommand[] | CommandOption[]; };} & T
Options, function createBooleanOption<R extends boolean, T extends SeyfertBooleanOption<R> = SeyfertBooleanOption<R>>(data: T): T & { readonly type: ApplicationCommandOptionType.Boolean;}
createBooleanOption, function createStringOption<R extends boolean, C extends SeyfertChoice<string>[] = SeyfertChoice<string>[], VC = never>(data: SeyfertStringOption<C, R, VC>): { readonly type: ApplicationCommandOptionType.String; readonly required?: R | undefined; readonly choices?: C | undefined; readonly value?: ValueCallback<ApplicationCommandOptionType.String, C, VC> | undefined; readonly description: string; readonly description_localizations?: APIApplicationCommandBasicOption["description_localizations"]; readonly name_localizations?: APIApplicationCommandBasicOption["name_localizations"]; readonly locales?: { name?: FlatObjectKeys<DefaultLocale>; description?: FlatObjectKeys<DefaultLocale>; }; readonly autocomplete?: AutocompleteCallback; readonly onAutocompleteError?: OnAutocompleteErrorCallback; readonly min_length?: number; readonly max_length?: number;}
createStringOption, type class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext} from 'seyfert';import { MessageFlags } from 'seyfert/lib/types';
const const options: { hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}
options = { hide: { description: string;} & { readonly type: ApplicationCommandOptionType.Boolean;}
hide: createBooleanOption<boolean, { description: string;}>(data: { description: string;}): { description: string;} & { readonly type: ApplicationCommandOptionType.Boolean;}
createBooleanOption({ description: string
description: "Hide command output", }), language: { readonly type: ApplicationCommandOptionType.String; readonly required?: boolean | undefined; readonly choices?: { ...; }[] | undefined; ... 8 more ...; readonly max_length?: number;}
language: createStringOption<boolean, { name: string; value: string;}[], never>(data: SeyfertStringOption<{ name: string; value: string;}[], boolean, never>): { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number;}
createStringOption({ SeyfertBaseChoiceableOption<T extends keyof ReturnOptionsTypes, C = T extends ChoiceableTypes ? SeyfertChoice<ChoiceableValues[T]>[] : never, R = boolean, VC = never>.description: string
description: "Language to respond in", SeyfertBaseChoiceableOption<ApplicationCommandOptionType.String, { name: string; value: string; }[], boolean, never>.choices?: { name: string; value: string;}[] | undefined
choices: [ { name: string
name: "English", value: string
value: "en" }, { name: string
name: "Spanish", value: string
value: "es" } ] })}
@function Declare(declare: CommandDeclareOptions): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { name: string; nsfw: boolean | undefined; props: ExtraProps | undefined; contexts: InteractionContextType[]; integrationTypes: ApplicationIntegrationType[]; defaultMemberPermissions: bigint | undefined; botPermissions: bigint | undefined; description: string; type: ApplicationCommandType; guildId?: string[]; ignore?: IgnoreCommand; aliases?: string[]; handler?: EntryPointCommandHandlerType; };} & T
Declare({ name: string
name: 'ping', description: string
description: 'Show the ping with discord'})@function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends { new (...args: any[]): object;}>(target: T) => { new (...args: any[]): { options: SubCommand[] | CommandOption[]; };} & T
Options(const options: { hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}
options)export default class class PingCommand
PingCommand extends class Command
Command {
async PingCommand.run(ctx: CommandContext<typeof options>): Promise<void>
run(ctx: CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}, never>
ctx: class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext<typeof const options: { hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}
options>) { const const flags: MessageFlags.Ephemeral | undefined
flags = ctx: CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}, never>
ctx.CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; }; }, never>.options: ContextOptions<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}>
options.hide?: boolean | undefined
hide ? MessageFlags.function (enum member) MessageFlags.Ephemeral = 64
This message is only visible to the user who invoked the Interaction
Ephemeral : var undefined
undefined; const const lang: string | undefined
lang = ctx: CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}, never>
ctx.CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; }; }, never>.options: ContextOptions<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}>
options.language?: string | undefined
language;
// Obtener las traducciones del idioma const const t: DefaultLocale
t = ctx: CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}, never>
ctx.CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; }; }, never>.t: __InternalParseLocale<DefaultLocale> & { get(locale?: string): DefaultLocale;}
t.function get(locale?: string): DefaultLocale
get(const lang: string | undefined
lang);
// latencia promedio entre shards const const ping: number
ping = ctx: CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}, never>
ctx.CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; }; }, never>.client: UsingClient
client.Client<true>.gateway: ShardManager
gateway.ShardManager.latency: number
latency;
await ctx: CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; };}, never>
ctx.CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; language: { readonly type: ApplicationCommandOptionType.String; ... 10 more ...; readonly max_length?: number; }; }, never>.write<false>(body: InteractionCreateBodyRequest, withResponse?: false | undefined): Promise<void | WebhookMessage>
write({ content?: string | undefined
The message contents (up to 2000 characters)
content: const t: DefaultLocale
t.foo: { ping(data: { ping: number; }): `Pong: ${number}`;}
foo.function ping(data: { ping: number;}): `Pong: ${number}`
ping({ ping: number
ping }), flags?: MessageFlags | undefined
Message flags combined as a bitfield
flags, }); }
}
A continuación se muestra el árbol de archivos actual del proyecto si ha seguido los pasos anteriores.
Directorysrc
Directorycommands
- ping.ts
Directoryevents
- botReady.ts
- guildDelete.ts
Directorylanguages
- en.ts
- es.ts
- index.ts
- package.json
- seyfert.config.mjs
- .env
- tsconfig.json