Supporting different languages
Seyfert has a i18n built-in feature that allows you to create language files and use them in your bot.
Updating seyfert config
Before starting this chapter we could update seyfert.config.mjs
to tell seyfert where our languages file will be.
// @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 will be our languages directory }});
Creating a language file
Each language file shall export by default an object containing the translations for the language.
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")}
You can create as many language files as you want, seyfert will load them and they will be available to use in your 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; // This is a utility type to ensure that object is the same across languages
Next we must do some updates to the declare module
on index file:
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> { } // If you are using the rest api
interface DefaultLocale extends ParseLocales<typeof English> { }}
After doing this, you can use the language in your commands, events, components, etc.
Using translations in your commands
Let’s see an example with our ping
command by adding an option to respond in a specific language
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;
// Get the translations for the language 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);
// average latency between 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, }); }
}
Below is the current file tree of the project if you did follow the previous steps.
Directorysrc
Directorycommands
- ping.ts
Directoryevents
- botReady.ts
- guildDelete.ts
Directorylanguages
- en.ts
- es.ts
- index.ts
- package.json
- seyfert.config.mjs
- .env
- tsconfig.json