import { Awaitable } from '@sapphire/utilities'; import { Backend } from '@skyra/i18next-backend'; import * as i18next from 'i18next'; import { InitOptions, TFunction, ParseKeys, TOptions, Namespace, DefaultNamespace, TFunctionReturn, AppendKeyPrefix, InterpolationMap, TFunctionReturnOptionalDetails } from 'i18next'; export { TFunction, TOptions, default as i18next } from 'i18next'; import { PathLike } from 'node:fs'; import { ChokidarOptions } from 'chokidar'; import { Guild, Message, StageChannel, VoiceChannel, User, Interaction, LocalizationMap, BaseInteraction, APIApplicationCommandOptionChoice } from 'discord.js'; /** * This is a re-exported type from i18next. * * We could use NoInfer typescript build-in utility, * however this project still supports ts < 5.4. * * @see https://github.com/millsp/ts-toolbelt/blob/master/sources/Function/NoInfer.ts */ type $NoInfer = [A][A extends any ? 0 : never]; /** * This is a re-exported type from i18next. * It is essentially an object of key-value pairs, where the key is a string and the value is any. */ interface $Dictionary { [key: string]: any; } /** * This is a re-exported type from i18next. * It is the returned type from `resolveKey` when `returnObjects` is `true` in the options. */ type $SpecialObject = $Dictionary | Array; /** * Configure whether to use Hot-Module-Replacement (HMR) for your i18next resources using these options. The minimum config to enable HMR is to set `enabled` to true. Any other properties are optional. * @since 2.2.0 */ interface HMROptions { /** * HMR status for the i18next plugin. * @default false */ enabled: boolean; /** * Languages that will be reloaded when updating the languages directory. * @default All languages that are automatically resolved from your folder setup */ languages?: string | string[]; /** * Namespaces that will be reloaded when updating the languages directory. * @default All namespaces that are automatically resolved from your languages folder setup */ namespaces?: string | string[]; /** * HMR options */ options?: ChokidarOptions; } /** * Used to dynamically add options based on found languages in {@link InternationalizationHandler#init}. * @since 1.1.0 * @private */ type DynamicOptions = (namespaces: string[], languages: string[]) => T; /** * The options used in {@link InternationalizationHandler}. * @since 1.0.0 */ interface InternationalizationOptions { /** * Used as the default 2nd to last fallback locale if no other is found. * It's only followed by "en-US". * @since 1.0.0 */ defaultName?: string; /** * The options passed to `backend` in `i18next.init`. * @since 1.0.0 */ backend?: Backend.Options; /** * The options passed to `i18next.init`. * @since 1.0.0 */ i18next?: InitOptions | DynamicOptions; /** * The directory in which "i18next-fs-backend" should search for files. * @default `rootDirectory/language` * @since 1.0.0 */ defaultLanguageDirectory?: string; /** * The default value to be used if a specific language key isn't found. * Defaults to "default:default". * @since 1.0.0 */ defaultMissingKey?: string; /** * The default NS that is prefixed to all keys that don't specify it. * Defaults to "default". * @since 1.0.0 */ defaultNS?: string; /** * Array of formatters to add to i18n. * * @since 2.0.0 * @default [] */ formatters?: I18nextFormatter[]; /** * Reload languages and namespaces when updating the languages directory. * * @since 2.2.0 */ hmr?: HMROptions; /** * A function that is to be used to retrieve the language for the current context. * Context exists of a {@link Guild `guild`}, a {@link DiscordChannel `channel`} and a {@link User `user`}. * * If this is not set, then the language will always be the default language. * * This will be inserted for {@link InternationalizationHandler.fetchLanguage}. * @since 2.0.0 * @default () => InternationalizationOptions.defaultName */ fetchLanguage?: (context: InternationalizationContext) => Awaitable; } type TextBasedDiscordChannel = Message['channel']; type DiscordChannel = TextBasedDiscordChannel | StageChannel | VoiceChannel; /** * Context for {@link InternationalizationHandler.fetchLanguage} functions. * This context enables implementation of per-guild, per-channel, and per-user localization. */ interface InternationalizationContext { /** The {@link Guild} object to fetch the preferred language for, or `null` if the language is to be fetched in a DM. */ guild: Guild | null; /** The {@link DiscordChannel} object to fetch the preferred language for. */ channel: DiscordChannel | null; /** The user to fetch the preferred language for. */ user: User | null; interactionGuildLocale?: Interaction['guildLocale']; interactionLocale?: Interaction['locale']; } interface InternationalizationClientOptions { i18n?: InternationalizationOptions; } /** @deprecated Use {@link I18nextFormatter} instead */ interface I18nextFormatters extends I18nextNamedFormatter { } /** * Represents a formatter that is added to i18next with `i18next.services.formatter.add` or `i18next.services.formatter.addCached`, * depending on the `cached` property. * * @since 7.1.0 * @seealso {@link https://www.i18next.com/translation-function/formatting#adding-custom-format-function} */ type I18nextFormatter = I18nextNamedFormatter | I18nextNamedCachedFormatter; /** * Represents a cached formatter that is added to i18next with `i18next.services.formatter.add`. * * @since 7.1.0 * @seealso {@link https://www.i18next.com/translation-function/formatting#adding-custom-format-function} */ interface I18nextNamedFormatter { cached?: false; name: string; format(value: any, lng: string | undefined, options: any): string; } /** * Represents a cached formatter that is added to i18next with `i18next.services.formatter.addCached`. * * @since 7.1.0 * @seealso {@link https://www.i18next.com/translation-function/formatting#adding-custom-format-function} */ interface I18nextNamedCachedFormatter { cached: true; name: string; format(lng: string | undefined, options: any): (value: any) => string; } interface LocalizedData { value: string; localizations: LocalizationMap; } interface BuilderWithName { setName(name: string): this; setNameLocalizations(localizedNames: LocalizationMap | null): this; } interface BuilderWithDescription { setDescription(description: string): this; setDescriptionLocalizations(localizedDescriptions: LocalizationMap | null): this; } type BuilderWithNameAndDescription = BuilderWithName & BuilderWithDescription; type ChannelTarget = Message | DiscordChannel; type Target = BaseInteraction | ChannelTarget | Guild; /** * A generalized class for handling `i18next` JSON files and their discovery. * @since 1.0.0 */ declare class InternationalizationHandler { /** * Describes whether {@link InternationalizationHandler.init} has been run and languages are loaded in {@link InternationalizationHandler.languages}. * @since 1.0.0 */ languagesLoaded: boolean; /** * A `Set` of initially loaded namespaces. * @since 1.2.0 */ namespaces: Set; /** * A `Map` of `i18next` language functions keyed by their language code. * @since 1.0.0 */ readonly languages: Map>; /** * The options InternationalizationHandler was initialized with in the client. * @since 1.0.0 */ readonly options: InternationalizationOptions; /** * The director passed to `@skyra/i18next-backend`. * Also used in {@link InternationalizationHandler.walkLanguageDirectory}. * @since 1.2.0 */ readonly languagesDirectory: string; /** * The backend options for `@skyra/i18next-backend` used by `i18next`. * @since 1.0.0 */ protected readonly backendOptions: Backend.Options; /** * @param options The options that `i18next`, `@skyra/i18next-backend`, and {@link InternationalizationHandler} should use. * @since 1.0.0 * @constructor */ constructor(options?: InternationalizationOptions); /** * The method to be overridden by the developer. * * @note In the event that fetchLanguage is not defined or returns null / undefined, the defaulting from {@link fetchLanguage} will be used. * @since 2.0.0 * @return A string for the desired language or null for no match. * @see {@link fetchLanguage} * @example * ```typescript * // Always use the same language (no per-guild configuration): * container.i18n.fetchLanguage = () => 'en-US'; * ``` * @example * ```typescript * // Retrieving the language from an SQL database: * container.i18n.fetchLanguage = async (context) => { * const guild = await driver.getOne('SELECT language FROM public.guild WHERE id = $1', [context.guild.id]); * return guild?.language ?? 'en-US'; * }; * ``` * @example * ```typescript * // Retrieving the language from an ORM: * container.i18n.fetchLanguage = async (context) => { * const guild = await driver.getRepository(GuildEntity).findOne({ id: context.guild.id }); * return guild?.language ?? 'en-US'; * }; * ``` * @example * ```typescript * // Retrieving the language on a per channel basis, e.g. per user or guild channel (ORM example but same principles apply): * container.i18n.fetchLanguage = async (context) => { * const channel = await driver.getRepository(ChannelEntity).findOne({ id: context.channel.id }); * return channel?.language ?? 'en-US'; * }; * ``` */ fetchLanguage: (context: InternationalizationContext) => Awaitable; /** * Initializes the handler by loading in the namespaces, passing the data to i18next, and filling in the {@link InternationalizationHandler#languages}. * @since 1.0.0 */ init(): Promise; /** * Retrieve a raw TFunction from the passed locale. * @param locale The language to be used. * @since 1.0.0 */ getT(locale: string): TFunction<"translation", undefined>; /** * Localizes a content given one or more keys and i18next options. * @since 2.0.0 * @param locale The language to be used. * @param key The key or keys to retrieve the content from. * @param options The interpolation options. * @see {@link https://www.i18next.com/overview/api#t} * @returns The localized content. */ format, const TOpt extends TOptions = TOptions, Ns extends Namespace = DefaultNamespace, Ret extends TFunctionReturn, TOpt> = TOpt['returnObjects'] extends true ? $SpecialObject : string, const ActualOptions extends TOpt & InterpolationMap = TOpt & InterpolationMap>(locale: string, key: Key | Key[], options?: ActualOptions): TFunctionReturnOptionalDetails; /** * Localizes a content given one or more keys and i18next options. * @since 2.0.0 * @param locale The language to be used. * @param key The key or keys to retrieve the content from. * @param options The interpolation options as well as a `defaultValue` for the key and any key/value pairs. * @see {@link https://www.i18next.com/overview/api#t} * @returns The localized content. */ format, const TOpt extends TOptions = TOptions, Ns extends Namespace = DefaultNamespace, Ret extends TFunctionReturn, TOpt> = TOpt['returnObjects'] extends true ? $SpecialObject : string, const ActualOptions extends TOpt & InterpolationMap = TOpt & InterpolationMap>(locale: string, key: string | string[], options: TOpt & $Dictionary & { defaultValue: string; }): TFunctionReturnOptionalDetails; /** * Localizes a content given one or more keys and i18next options. * @since 2.0.0 * @param locale The language to be used. * @param key The key or keys to retrieve the content from. * @param defaultValue The default value to use if the key is not found. * @param options The interpolation options. * @see {@link https://www.i18next.com/overview/api#t} * @returns The localized content. */ format, const TOpt extends TOptions = TOptions, Ns extends Namespace = DefaultNamespace, Ret extends TFunctionReturn, TOpt> = TOpt['returnObjects'] extends true ? $SpecialObject : string, const ActualOptions extends TOpt & InterpolationMap = TOpt & InterpolationMap>(locale: string, key: string | string[], defaultValue: string | undefined, options?: TOpt & $Dictionary): TFunctionReturnOptionalDetails; /** * @param directory The directory that should be walked. * @since 3.0.0 */ walkRootDirectory(directory: PathLike): Promise<{ namespaces: string[]; languages: string[]; }>; reloadResources(): Promise; /** * @description Skips any files that don't end with `.json`. * @param directory The directory that should be walked. * @param ns The current namespace. * @since 3.0.0 */ private walkLocaleDirectory; } /** * Retrieves the language name for a specific target, using {@link InternationalizationHandler.fetchLanguage}. * If {@link InternationalizationHandler.fetchLanguage} is not defined or this function returns a nullish value, * then there will be a series of fallback attempts in the following descending order: * 1. Returns {@link Guild.preferredLocale}. * 2. Returns {@link InternationalizationOptions.defaultName} if no guild was provided. * 3. Returns `'en-US'` if nothing else was found. * @since 2.0.0 * @param target The target to fetch the language from. * @see {@link resolveLanguage} * @returns The name of the language key. */ declare function fetchLanguage(target: Target): Promise; /** * Retrieves the language-assigned function from i18next designated to a target's preferred language code. * @since 2.0.0 * @param target The target to fetch the language from. * @returns The language function from i18next. */ declare function fetchT(target: Target): Promise>; /** * Resolves a key and its parameters. * @since 2.0.0 * @param target The target to fetch the language key from. * @param key The i18next key. * @param options The options to be passed to TFunction. * @returns The data that `key` held, processed by i18next. */ declare function resolveKey, const TOpt extends TOptions = TOptions, Ret extends TFunctionReturn, TOpt> = TOpt['returnObjects'] extends true ? $SpecialObject : string, Ns extends Namespace = DefaultNamespace, const ActualOptions extends TOpt & InterpolationMap = TOpt & InterpolationMap>(target: Target, key: Key | Key[], options?: ActualOptions): Promise>; /** * Resolves a key and its parameters. * @since 2.0.0 * @param target The target to fetch the language key from. * @param key The i18next key. * @param options The interpolation options as well as a `defaultValue` for the key and any key/value pairs. * @returns The data that `key` held, processed by i18next. */ declare function resolveKey, const TOpt extends TOptions = TOptions, Ret extends TFunctionReturn, TOpt> = TOpt['returnObjects'] extends true ? $SpecialObject : string, Ns extends Namespace = DefaultNamespace, const ActualOptions extends TOpt & InterpolationMap = TOpt & InterpolationMap>(target: Target, key: string | string[], options: TOpt & $Dictionary & { defaultValue: string; }): Promise>; /** * Resolves a key and its parameters. * @since 2.0.0 * @param target The target to fetch the language key from. * @param key The i18next key. * @param defaultValue The default value to use if the key is not found. * @param options The interpolation options. * @returns The data that `key` held, processed by i18next. */ declare function resolveKey, const TOpt extends TOptions = TOptions, Ret extends TFunctionReturn, TOpt> = TOpt['returnObjects'] extends true ? $SpecialObject : string, Ns extends Namespace = DefaultNamespace, const ActualOptions extends TOpt & InterpolationMap = TOpt & InterpolationMap>(target: Target, key: string | string[], defaultValue: string, options?: TOpt & $Dictionary): Promise>; /** * Gets the value and the localizations from a language key. * @param key The key to get the localizations from. * @returns The retrieved data. * @remarks This should be called **strictly** after loading the locales. */ declare function getLocalizedData(key: ParseKeys): LocalizedData; /** * Applies the localized names on the builder, calling `setName` and `setNameLocalizations`. * @param builder The builder to apply the localizations to. * @param key The key to get the localizations from. * @returns The updated builder. */ declare function applyNameLocalizedBuilder(builder: T, key: ParseKeys): T; /** * Applies the localized descriptions on the builder, calling `setDescription` and `setDescriptionLocalizations`. * @param builder The builder to apply the localizations to. * @param key The key to get the localizations from. * @returns The updated builder. */ declare function applyDescriptionLocalizedBuilder(builder: T, key: ParseKeys): T; /** * Applies the localized names and descriptions on the builder, calling {@link applyNameLocalizedBuilder} and * {@link applyDescriptionLocalizedBuilder}. * * @param builder The builder to apply the localizations to. * * @param params The root key or the key for the name and description keys. * This needs to be either 1 or 2 parameters. * See examples below for more information. * * @returns The updated builder. You can chain subsequent builder methods on this. * * @remarks If only 2 parameters were passed, then this function will automatically append `Name` and `Description` * to the root-key (wherein `root-key` is second parameter in the function, after `builder`) * passed through the second parameter. * * For example given `applyLocalizedBuilder(builder, 'userinfo')` the localized options will use the i18next keys * `userinfoName` and `userinfoDescription`. * * In the following example we provide all parameters and add a User Option * `applyLocalizedBuilder` needs either * @example * ```typescript * class UserInfoCommand extends Command { * public registerApplicationCommands(registry: ChatInputCommand.Registry) { * registry.registerChatInputCommand( * (builder) => * applyLocalizedBuilder(builder, 'commands/names:userinfo', 'commands/descriptions:userinfo') * .addUserOption( * (input) => applyLocalizedBuilder(input, 'commands/options:userinfo-name', 'commands/options:userinfo-description').setRequired(true) * ) * ); * } * } * ``` * * In the following example we provide single root keys which means `Name` and `Description` get appended as mentioned above. * @example * ```typescript * class UserInfoCommand extends Command { * public registerApplicationCommands(registry: ChatInputCommand.Registry) { * registry.registerChatInputCommand( * (builder) => * applyLocalizedBuilder(builder, 'commands:userinfo') * .addUserOption( * (input) => applyLocalizedBuilder(input, 'options:userinfo').setRequired(true) * ) * ); * } * } * ``` */ declare function applyLocalizedBuilder(builder: T, ...params: [root: string] | [name: ParseKeys, description: ParseKeys]): T; /** * Constructs an object that can be passed into `setChoices` for String or Number option with localized names. * * @param key The i18next key for the name of the select option name. * @param options The additional Select Menu options. This should _at least_ include the `value` key. * @returns An object with anything provided through {@link createLocalizedChoice.options} with `name` and `name_localizations` added. * * @example * ```typescript * export class TypeCommand extends Command { * public override registerApplicationCommands(registry: ChatInputCommand.Registry) { * registry.registerChatInputCommand((builder) => * applyLocalizedBuilder(builder, 'commands/names:type').addStringOption((option) => * applyLocalizedBuilder(option, 'commands/options:type') * .setRequired(true) * .setChoices( * createLocalizedChoice('selects/pokemon:type-grass', { value: 'grass' }), * createLocalizedChoice('selects/pokemon:type-water', { value: 'water' }), * createLocalizedChoice('selects/pokemon:type-fire', { value: 'fire' }), * createLocalizedChoice('selects/pokemon:type-electric', { value: 'electric' }) * ) * ) * ); * } * } * ``` */ declare function createLocalizedChoice(key: ParseKeys, options: Omit, 'name' | 'name_localizations'>): APIApplicationCommandOptionChoice; declare module '@sapphire/pieces' { interface Container { i18n: InternationalizationHandler; } } declare module 'discord.js' { interface ClientOptions extends InternationalizationClientOptions { } } /** * The [@sapphire/plugin-i18next](https://github.com/sapphiredev/plugins/blob/main/packages/i18next) version that you are currently using. * An example use of this is showing it of in a bot information command. * * Note to Sapphire developers: This needs to explicitly be `string` so it is not typed as the string that gets replaced by esbuild */ declare const version: string; export { type $Dictionary, type $NoInfer, type $SpecialObject, type BuilderWithDescription, type BuilderWithName, type BuilderWithNameAndDescription, type ChannelTarget, type DiscordChannel, type DynamicOptions, type HMROptions, type I18nextFormatter, type I18nextFormatters, type I18nextNamedCachedFormatter, type I18nextNamedFormatter, type InternationalizationClientOptions, type InternationalizationContext, InternationalizationHandler, type InternationalizationOptions, type LocalizedData, type Target, type TextBasedDiscordChannel, applyDescriptionLocalizedBuilder, applyLocalizedBuilder, applyNameLocalizedBuilder, createLocalizedChoice, fetchLanguage, fetchT, getLocalizedData, resolveKey, version };