├── .gitignore
├── LICENSE
├── README.md
├── command
└── lang
│ ├── setlang.js
│ └── test.js
├── event
├── messageCreate.js
└── ready.js
├── exemple.env
├── index.js
├── language
├── en.js
└── fr.js
├── package.json
└── utils
├── CustomError.js
├── color.js
├── handlers
├── command.js
├── error.js
└── event.js
└── logger.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env
3 | package-lock.json
4 | json.sqlite
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Melio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Multi-Language-Discord-Bot
2 |
3 | Multi-language Discord Bot (v14) example (fr/en) with command & event handler, by Melio .
4 |
5 | If you like the project, feel free to put a ⭐ for better referencing ; If you need help, join the support server .
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## About
24 | This program is an example of internationalization integration for a Discord Bot.
25 | It's a very simplified example of how to implement several languages in one application.
26 |
27 | Again with a focus on simple code, data is stored with SQLite, via the [quick.db](https://www.npmjs.com/package/quick.db) package.
28 | Of course, you're free to implement any other DBMS (database management system).
29 |
30 | ## Starting the project
31 |
32 | ### Configuration
33 | The configuration file named **.env** have to be created and replace **exemple.env** and content the token of your discord bot, his prefix and your id or the ids of all other owners.
34 | ```
35 | # Bot configuration
36 |
37 | PREFIX=BOT_PREFIX
38 | TOKEN=BOT_TOKEN
39 | OWNER=YOUR_DISCORD_ID
40 | ```
41 |
42 | ### Installation
43 | ```sh
44 | $ npm install
45 | ```
46 |
47 | ### Start the bot
48 | ```sh
49 | $ node index.js
50 | ```
51 |
52 |
53 |
54 | ## More
55 |
56 | For any errors found, please contact me [here](https://discord.com/invite/G6WQsMQShZ) or do a pull request.
57 | This repository is licensed under the MIT License. See the `LICENSE` file ([here](LICENSE)) for more information.
58 |
59 | ###### This repository was made with ❤️ from my [Structure-Discord-Bot](https://github.com/antoinemcx/Structure-Discord-Bot/tree/master) template repository.
60 |
--------------------------------------------------------------------------------
/command/lang/setlang.js:
--------------------------------------------------------------------------------
1 | const { QuickDB } = require("quick.db");
2 |
3 | module.exports = {
4 | conf: {
5 | name: "setlang",
6 | description: "setlang",
7 | usage: "setlang ",
8 | aliases: ["sl", "lang"],
9 | cooldown: 2, // (Time in second)
10 | dir: "lang",
11 | },
12 | run: async (bot, message, args) => {
13 | const db = new QuickDB();
14 | await db.init();
15 |
16 | let messageToSend;
17 |
18 | if(!args[0]) {
19 | messageToSend = bot.language.SETLANG_ERR;
20 | } else if(args[0] === "en") {
21 | await db.set(`lang_${message.guild.id}`, { lang: "en" })
22 | messageToSend = bot.language.SETLANG_SUCCESS[1];
23 | } else if(args[0] === "fr") {
24 | await db.set(`lang_${message.guild.id}`, { lang: "fr" });
25 | messageToSend = bot.language.SETLANG_SUCCESS[0];
26 | } else {
27 | messageToSend = bot.language.SETLANG_ERR;
28 | }
29 |
30 | message.channel.send(messageToSend);
31 | }
32 | }
--------------------------------------------------------------------------------
/command/lang/test.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | conf: {
3 | name: "test",
4 | description: "test",
5 | usage: "test",
6 | aliases: ["t"],
7 | dir: "lang",
8 | },
9 | run: async (bot, message, args) => {
10 | message.channel.send(bot.language.TEST_MESSAGE)
11 | }
12 | }
--------------------------------------------------------------------------------
/event/messageCreate.js:
--------------------------------------------------------------------------------
1 | const { Collection } = require("discord.js");
2 | require('dotenv').config()
3 | const prefix = process.env.PREFIX;
4 | const { QuickDB } = require("quick.db");
5 | const db = new QuickDB();
6 |
7 | module.exports = async (bot, message) => {
8 | if (message.author.bot) { return }
9 |
10 | //LANGUAGE
11 | await db.init();
12 |
13 | default_lang = "en"
14 | lang_query = await db.get(`lang_${message.guild.id}`);
15 | bot.language = require(`../language/${lang_query === null ? default_lang : lang_query.lang}.js`);
16 |
17 | if(message.content.match(new RegExp(`^<@!?${bot.user.id}>( |)$`))) { // bot mentionned in the message
18 | message.channel.send(bot.language.MENTION_BOT.replace(//g, bot.user.username)
19 | .replace(//g, prefix));
20 |
21 | } else if (message.content.startsWith(prefix)) { // bot prefix used
22 | const command = message.content.split(' ')[0].slice(prefix.length).toLowerCase();
23 | const args = message.content.split(' ').slice(1);
24 | let cmd;
25 |
26 | if (bot.commandes.has(command)) {
27 | cmd = bot.commandes.get(command);
28 | } else if (bot.aliases.has(command)) {
29 | cmd = bot.commandes.get(bot.aliases.get(command));
30 | }
31 | if(!cmd) return;
32 |
33 | const props = require(`../command/${cmd.conf.dir}/${cmd.conf.name}`);
34 |
35 | // COOLDOWNS
36 | if (!cooldowns.has(props.conf.name)) {
37 | cooldowns.set(props.conf.name, new Collection()); // cooldown creation
38 | }
39 |
40 | const now = Date.now();
41 | const timestamps = cooldowns.get(props.conf.name);
42 | const cooldownAmount = (props.conf.cooldown || 1) * 1000;
43 |
44 | if (timestamps.has(message.author.id)) {
45 | let expirationTime = timestamps.get(message.author.id) + cooldownAmount;
46 |
47 | if (now < expirationTime) {
48 | const timeLeft = (expirationTime - now) / 1000;
49 | return message.channel.send(bot.language.COOLDOWN_ERR.replace(//g, timeLeft.toFixed(1))
50 | .replace(//g, props.conf.name));
51 | }
52 | } else {
53 | timestamps.set(message.author.id, now); // addition of the cooldown to the user
54 | }
55 | setTimeout(() => timestamps.delete(message.author.id), cooldownAmount);
56 |
57 | // LOADING COMMANDS
58 | try {
59 | cmd.run(bot, message, args);
60 | } catch (e) {
61 | bot.emit("error", e, message);
62 | }
63 | }
64 | };
--------------------------------------------------------------------------------
/event/ready.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 | const prefix = process.env.PREFIX;
3 |
4 | module.exports = async (bot) => {
5 | bot.logger.info("-----------------------------------------")
6 | bot.logger.info(`[!] Successfully connected on ${bot.user.username} !`)
7 | bot.logger.info("-----------------------------------------")
8 |
9 | setInterval(() => {
10 | bot.user.setActivity(`${process.env.NAME} | ${prefix} | github.com/antoinemcx`, { type: "PLAYING" });
11 | });
12 | };
13 |
--------------------------------------------------------------------------------
/exemple.env:
--------------------------------------------------------------------------------
1 | # Bot configuration
2 |
3 | PREFIX=BOT_PREFIX
4 | TOKEN=BOT_TOKEN
5 | OWNER=YOUR_DISCORD_ID
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const { Client, Collection, GatewayIntentBits } = require("discord.js");
2 | const bot = new Client({
3 | allowedMentions: { parse: ['users', 'roles'] },
4 | fetchAllMembers: false,
5 | intents: [
6 | GatewayIntentBits.Guilds,
7 | GatewayIntentBits.GuildMembers,
8 | GatewayIntentBits.GuildMessages,
9 | GatewayIntentBits.MessageContent
10 | ],
11 | });
12 | require('dotenv').config()
13 |
14 | // SET COLLECTION & MAP
15 | bot.commandes = new Collection();
16 | bot.aliases = new Collection();
17 | cooldowns = new Collection();
18 |
19 | // SET UTILS
20 | bot.logger = require('./utils/logger');
21 | bot.color = require('./utils/color');
22 |
23 | // LOAD THE HANDLERS
24 | ["error", "command", "event"].forEach(file => require(`./utils/handlers/${file}`)(bot));
25 |
26 | bot.login(process.env.TOKEN);
--------------------------------------------------------------------------------
/language/en.js:
--------------------------------------------------------------------------------
1 | module.exports= {
2 | // DEFAULT //
3 | MENTION_BOT: `I'm and my prefix is \`\``,
4 | COOLDOWN_ERR: `You must wait more **** second(s) before use the command \`\``,
5 |
6 | // SETLANG COMMAND //
7 | SETLANG_ERR: "Please specifie a correct new language to choose (fr or en)",
8 | SETLANG_SUCCESS:[
9 | "La langue est désormais en français",
10 | "The language is now english",
11 | ],
12 |
13 | // TEST COMMAND //
14 | TEST_MESSAGE: "I speak english",
15 | }
--------------------------------------------------------------------------------
/language/fr.js:
--------------------------------------------------------------------------------
1 | module.exports= {
2 | // DEFAULT //
3 | MENTION_BOT: `Je suis et mon préfix est \`\``,
4 | COOLDOWN_ERR: `Vous devez attendre **** seconde(s) de plus avant d'utiliser la commande \`\``,
5 |
6 | // SETLANG COMMAND //
7 | SETLANG_ERR: "Veuillez indiquer une nouvelle langue valide (fr ou en) à saisir",
8 | SETLANG_SUCCESS:[
9 | "La langue est désormais en français",
10 | "The language is now english",
11 | ],
12 |
13 | // TEST COMMAND //
14 | TEST_MESSAGE: "Je parle français",
15 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "musicbot",
3 | "version": "1.0.0",
4 | "description": "test bot",
5 | "main": "index.js",
6 | "dependencies": {
7 | "better-sqlite3": "^11.5.0",
8 | "chalk": "^4.1.2",
9 | "discord.js": "^14.16.3",
10 | "dotenv": "^16.4.5",
11 | "fs": "^0.0.1-security",
12 | "moment": "^2.30.1",
13 | "ms": "^2.1.3",
14 | "os": "^0.1.2",
15 | "parse-ms": "^4.0.0",
16 | "quick.db": "^9.1.7"
17 | },
18 | "scripts": {
19 | "test": "echo \"Error: no test specified\" && exit 1"
20 | },
21 | "author": "MélioOff",
22 | "license": "ISC"
23 | }
24 |
--------------------------------------------------------------------------------
/utils/CustomError.js:
--------------------------------------------------------------------------------
1 | function LoggerError(message) {
2 | this.name = this.constructor.name;
3 | this.message = message;
4 | Error.captureStackTrace(this, this.message)
5 | }
6 |
7 | function ChalkError(message){
8 | this.name = this.constructor.name;
9 | this.message = message;
10 | Error.captureStackTrace(this, this.message)
11 | }
12 |
13 | function SomeError(message){
14 | this.name = this.constructor.name;
15 | this.message = message;
16 | Error.captureStackTrace(this, this.message)
17 | }
18 |
19 |
20 | module.exports = {
21 | LoggerError,
22 | ChalkError,
23 | SomeError
24 | };
25 |
--------------------------------------------------------------------------------
/utils/color.js:
--------------------------------------------------------------------------------
1 | const {ChalkError} = require('./CustomError');
2 | const chalk = require('chalk');
3 | let chalkcolor ={
4 | red(message){
5 | if(!message) throw new ChalkError('Not text found !');
6 | return chalk.red(message)
7 | },
8 | black(message){
9 | if(!message) throw new ChalkError('Not text found !');
10 | return chalk.black(message)
11 | },
12 | green(message){
13 | if(!message) throw new ChalkError('Not text found !');
14 | return chalk.green(message)
15 | },
16 | yellow(message){
17 | if(!message) throw new ChalkError('Not text found !');
18 | return chalk.yellow(message)
19 | },
20 | magenta(message){
21 | if(!message) throw new ChalkError('Not text found !');
22 | return chalk.magenta(message)
23 | },
24 | blue(message){
25 | if(!message) throw new ChalkError('Not text found !');
26 | return chalk.blue(message)
27 | },
28 | cyanBright(message){
29 | if(!message) throw new ChalkError('Not text found !');
30 | return chalk.cyanBright(message)
31 | },
32 | };
33 |
34 | let messagecolor={
35 | red : 0xF52E2E,
36 | yellow: 0xF5F52E,
37 | orange: 0xF5AD2E,
38 | green: 0x76D813,
39 | cyan: 0x13D8CF,
40 | blue : 0x33A2FF,
41 | darkblue: 0x131CD8,
42 | purple:0x8A13D8,
43 | pink:0xD813D8,
44 | white: 0xFFFFFF,
45 | gray:0x9E9E9E,
46 | black:0x000000,
47 | blurple: 0x7289DA,
48 | greyple:0x99AAB5
49 | };
50 |
51 |
52 | module.exports= {
53 | chalkcolor,
54 | messagecolor
55 | };
56 |
--------------------------------------------------------------------------------
/utils/handlers/command.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (bot) => {
4 | const commandDirectory = `${__dirname}/../../command`;
5 | fs.readdir(commandDirectory + '/', (err, files) => {
6 | if (err) bot.logger.error(err);
7 | // Command category loading
8 | files.forEach(dir => {
9 | fs.readdir(`${commandDirectory}/${dir}/`, (err, file) => {
10 | if (err) bot.logger.error(err);
11 | bot.logger.loader(`${bot.color.chalkcolor.magenta('[CATEGORY] ')} ${bot.color.chalkcolor.blue(`${dir}`)} loading...`);
12 |
13 | // Command loading
14 | file.forEach(f => {
15 | const props = require(`${commandDirectory}/${dir}/${f}`);
16 | bot.logger.loader(`[COMMANDE] ${bot.color.chalkcolor.cyanBright(`${f}`)} is loaded`);
17 | bot.commandes.set(props.conf.name, props);
18 | props.conf.aliases.forEach(alias => {
19 | bot.aliases.set(alias, props.conf.name);
20 | });
21 | });
22 |
23 | bot.logger.loader(`${bot.color.chalkcolor.magenta('[CATEGORY]')} ${bot.color.chalkcolor.red('[FINISH]')} ${bot.color.chalkcolor.blue(`${dir}`)} is loaded`)
24 | });
25 | });
26 | });
27 | };
--------------------------------------------------------------------------------
/utils/handlers/error.js:
--------------------------------------------------------------------------------
1 | module.exports = async (client) => {
2 | process.on('unhandledRejection', err => {
3 | if(!err) return
4 |
5 | if(err instanceof Error) {
6 | if (err.stack.includes('An invalid token was provided.')) {
7 | return client.logger.error('Bad token see config.js for set the token')
8 | } else {
9 | return client.logger.error(err.stack)
10 | }
11 | }
12 |
13 | client.logger.error(err)
14 | });
15 |
16 | process.on('uncaughtException', err =>{
17 | if (err.stack.includes('Promise { }')) return;
18 | return client.logger.error(err.stack)
19 | });
20 |
21 | process.on('warning', (err) => {
22 | client.logger.error(err.stack)
23 | })
24 | }
--------------------------------------------------------------------------------
/utils/handlers/event.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (bot) => {
4 | const eventDirectory = `${__dirname}/../../event`;
5 |
6 | fs.readdir(eventDirectory + '/', (err, files) => {
7 | if (err) bot.logger.error(err);
8 | files.forEach(file => {
9 | const event = require(`${eventDirectory}/${file}`);
10 | let eventName = file.split(".")[0];
11 | bot.logger.loader(`[EVENT] ${bot.color.chalkcolor.green(`${eventName}.js`)} is loaded`);
12 | bot.on(eventName, event.bind(null, bot));
13 | });
14 | bot.logger.loader(`[EVENT] ${bot.color.chalkcolor.red('[FINISH]')} ${files.length} events loaded`)
15 | });
16 | };
--------------------------------------------------------------------------------
/utils/logger.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 | const moment = require("moment");
3 | const { LoggerError } = require('./CustomError');
4 | const timestamp = `[${moment().format(" HH:mm:ss | DD-MM-YYYY")}]`;
5 |
6 | const noTextFound = "No text found";
7 |
8 | function log(content){
9 | if(!content) throw new LoggerError(noTextFound);
10 | console.log(`${chalk.cyan(timestamp)} ${chalk.blue.underline(('[LOG]'))} ${content}`)
11 | }
12 |
13 | function loader(content){
14 | if(!content) throw new LoggerError(noTextFound);
15 | console.log(`${chalk.cyan(timestamp)} ${chalk.green.underline(('[LOADER]'))} ${content}`)
16 | }
17 |
18 | function error(content){
19 | if(!content) throw new LoggerError(noTextFound);
20 | console.log(`${chalk.cyan(timestamp)} ${chalk.red.underline(('[ERROR]'))} ${content}`)
21 | }
22 |
23 | function warn(content){
24 | if(!content) throw new LoggerError(noTextFound);
25 | console.log(`${chalk.cyan(timestamp)} ${chalk.yellow.underline(('[WARN]'))} ${content}`)
26 | }
27 |
28 | function info(content){
29 | if(!content) throw new LoggerError(noTextFound);
30 | console.log(`${chalk.cyan(timestamp)} ${chalk.magenta.underline(('[INFO]'))} ${content}`)
31 | }
32 |
33 | function database(content){
34 | if(!content) throw new LoggerError(noTextFound);
35 | console.log(`${chalk.cyan(timestamp)} ${chalk.yellowBright.underline(('[DATABASE]'))} ${content}`)
36 | }
37 |
38 | module.exports = {
39 | log,
40 | loader,
41 | error,
42 | warn,
43 | info,
44 | database
45 | };
46 |
--------------------------------------------------------------------------------