Saltearse al contenido

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.

seyfert.config.mjs
// @ts-check
import {
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.

@paramdata - The runtime configuration data for gateway connections.

@returnsThe internal runtime configuration.

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.

@sincev0.1.27

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.

languages/en.ts
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.

@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.

join
("\n")
}

Puedes crear tantos archivos de idioma como quieras, seyfert los cargará y estarán disponibles para usar en tu bot.

languages/es.ts
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:

src/index.ts
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

src/commands/ping.ts
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