From 3398f3fb2962c94dc0fa439390736ac2558106f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B4nio=20Miranda?= Date: Sat, 23 Aug 2025 20:43:49 -0300 Subject: [PATCH] =?UTF-8?q?Adds=20support=20for=20multiple=20languages=20?= =?UTF-8?q?=E2=80=8B=E2=80=8Band=20environment=20settings=20for=20the=20bo?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implements locale file loading for bot messages. - Adds English and Portuguese locale files. - Updates bot messages to use appropriate translations. - Creates docker-compose.yml file to make it easier to run the bot in development. - Updates Dockerfile to include locale directory. --- .env.exemple | 3 +- Dockerfile | 1 + bot.py | 78 ++++++++++++++++++++++++++++++++-------------- docker-compose.yml | 8 +++++ locales/README.md | 27 ++++++++++++++++ locales/en_US.json | 14 +++++++++ locales/pt_BR.json | 14 +++++++++ 7 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 docker-compose.yml create mode 100644 locales/README.md create mode 100644 locales/en_US.json create mode 100644 locales/pt_BR.json diff --git a/.env.exemple b/.env.exemple index d2bdac5..c4ec087 100644 --- a/.env.exemple +++ b/.env.exemple @@ -4,4 +4,5 @@ CHANNEL_2=Your_Channel_ID_Here CHANNEL_3=Your_Channel_ID_Here NOTIFICATION_CHANNEL=Your_NOTIFICATION_Channel_ID_Here # This is the channel where the bot will send the notifications RICH_PRESENCE=Katchau! # This is the rich presence of the bot (example: "Katchau!") as a string -ACTIVITY=listening # This is the activity of the bot, you can use "game", "listening", "watching" or "streaming") \ No newline at end of file +ACTIVITY=listening # This is the activity of the bot, you can use "game", "listening", "watching" or "streaming") +BOT_LOCALE=en_US #(Optional) used to translate the bot messages. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9bcf654..2472fe6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ WORKDIR /app # Copy the necessary files to the bot container COPY bot.py /app COPY requirements.txt /app +COPY locales /app/locales # Install dependencies RUN pip install --no-cache-dir -r requirements.txt diff --git a/bot.py b/bot.py index a51daf1..b8a7575 100644 --- a/bot.py +++ b/bot.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta import operator import os +import json from dotenv import load_dotenv import sqlite3 @@ -14,9 +15,25 @@ intents.voice_states = True intents.members = True -client = commands.Bot(command_prefix='!', intents=intents) +client = commands.Bot(command_prefix=None, intents=intents) slash = SlashCommand(client) +# Load locales +def load_locale(lang=None): + if not lang: + lang = os.getenv('BOT_LOCALE', 'en_US') + + try: + with open(f'locales/{lang}.json', 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + # Fallback to English if locale file not found + with open('locales/en_US.json', 'r', encoding='utf-8') as f: + return json.load(f) + +# Load locale from environment variable +locale = load_locale() + # Connect to SQLite database conn = sqlite3.connect('bot_database.db') cursor = conn.cursor() @@ -30,6 +47,7 @@ last_call_time TIMESTAMP ) ''') + conn.commit() # Dictionary of messages per channel @@ -38,7 +56,7 @@ channel_env = f'CHANNEL_{i}' channel_id = os.getenv(channel_env) if channel_id: - channel_messages[channel_id] = "{member.name} is in {channel_name}" + channel_messages[channel_id] = locale["user_in_channel"].format(member="{member.name}", channel="{channel_name}") else: break # Stop the loop if there are no more defined channels @@ -74,10 +92,19 @@ async def on_voice_state_update(member, before, after): if before.channel == after.channel: return # Ignore updates that don't involve channel changes + notification_channel_id = os.getenv('NOTIFICATION_CHANNEL') + if not notification_channel_id: + return + + channel = client.get_channel(int(notification_channel_id)) + if not channel: + return + + # User joined a voice channel if after.channel: if len(after.channel.members) == 1: - channel = client.get_channel(int(os.getenv('NOTIFICATION_CHANNEL'))) - await channel.send(f"{member.name} is in {after.channel.name}") + # First person in the channel - mark with @everyone + await channel.send(locale["user_joined_call"].format(member=member.mention)) # Update entry count and last call time in the database cursor.execute(''' @@ -89,26 +116,31 @@ async def on_voice_state_update(member, before, after): if show_log: print(f"{member.name} entered a channel.") else: - cursor.execute('SELECT last_call_time FROM users WHERE id = ?', (member.id,)) - result = cursor.fetchone() - last_call_time = datetime.fromisoformat(result[0]) if result else datetime.min - if datetime.now() - last_call_time >= timedelta(minutes=60): - cursor.execute('UPDATE users SET last_call_time = ? WHERE id = ?', (datetime.now(), member.id)) - conn.commit() - channel = client.get_channel(1206713130353295450) - if not before.channel: - await channel.send(f"{member.mention} is in the call! @here") + # Other people joining - just show channel info + await channel.send(locale["user_in_channel"].format(member=member.name, channel=after.channel.name)) + + # User left a voice channel + if before.channel and not after.channel: + await channel.send(locale["user_left_channel"].format(member=member.name, channel=before.channel.name)) - if before.channel and after.channel and before.channel != after.channel and show_log: - print(f"{member.name} was moved from {before.channel.name} to {after.channel.name}") + # User moved between channels + if before.channel and after.channel and before.channel != after.channel: + await channel.send(locale["user_moved_to_channel"].format( + member=member.name, + old_channel=before.channel.name, + new_channel=after.channel.name + )) + + if show_log: + print(f"{member.name} was moved from {before.channel.name} to {after.channel.name}") @slash.slash(name="leaders", description="Shows how many times each user entered calls.") async def leaders(ctx): cursor.execute('SELECT id, name, entry_count FROM users ORDER BY entry_count DESC LIMIT 10') leaderboard = cursor.fetchall() - leaderboard_text = "Entry Leaderboard:\n" + leaderboard_text = locale["leaderboard_title"] + "\n" for idx, (user_id, name, count) in enumerate(leaderboard, start=1): - leaderboard_text += f"{idx}. {name}: {count} times\n" + leaderboard_text += locale["leaderboard_entry"].format(position=idx, name=name, count=count) + "\n" await ctx.send(content=leaderboard_text) if show_log: print('Leaderboard command executed.') @@ -117,18 +149,18 @@ async def leaders(ctx): async def toggle_message(ctx): global send_message_enabled send_message_enabled = not send_message_enabled - status = "enabled" if send_message_enabled else "disabled" - await ctx.send(content=f"Functionality to send a message when someone enters a call is now {status}.") + status = locale["toggle_enabled"] if send_message_enabled else locale["toggle_disabled"] + await ctx.send(content=status) @slash.slash(name="help", description="Get a list of commands.") async def help(ctx): commands = [ - "/leaders - Shows how many times each user entered calls.", - "/toggle - Turns on/off the functionality of sending a message when someone enters a call.", - "/help - Get a list of commands." + locale["help_leaders"], + locale["help_toggle"], + locale["help_help"] ] command_list = "\n".join(commands) - await ctx.send(content=f"Hello, {ctx.author.name}! Here's the list of available commands:\n\n{command_list}") + await ctx.send(content=locale["help_title"].format(author=ctx.author.name) + "\n\n" + command_list) # Load the bot token from environment variables TOKEN = os.getenv('DISCORD_BOT_TOKEN') diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..af9bdcc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + bot: + container_name: esk + build: + context: . + restart: unless-stopped + env_file: + - .env diff --git a/locales/README.md b/locales/README.md new file mode 100644 index 0000000..3afce4a --- /dev/null +++ b/locales/README.md @@ -0,0 +1,27 @@ +## Locales + +By default, the bot will use the English locale, and have the portuguese locale available as an alternative. + +If you want to use a different locale, you can create a new file in the `locales` folder with the name of the locale you want to use, and then set the `BOT_LOCALE` environment variable to the name of the locale you want to use. + +The locale file must be a JSON file with the following structure as an example: + +```json +{ + "user_joined_call": "{member} joined the call! @everyone", + "user_in_channel": "{member} joined {channel}", + "user_left_channel": "{member} left {channel}", + "user_moved_to_channel": "{member} left {old_channel} and went to {new_channel}", + "leaderboard_title": "Entry Leaderboard:", + "leaderboard_entry": "{position}. {name}: {count} times", + "toggle_enabled": "Functionality to send a message when someone enters a call is now enabled.", + "toggle_disabled": "Functionality to send a message when someone enters a call is now disabled.", + "help_title": "Hello, {author}! Here's the list of available commands:", + "help_leaders": "/leaders - Shows how many times each user entered calls.", + "help_toggle": "/toggle - Turns on/off the functionality of sending a message when someone enters a call.", + "help_help": "/help - Get a list of commands." +} + +``` + +[NOTE] Keep an eye on the `en_US.json` file, as it could be updated in the future and you might need to update your locale file. \ No newline at end of file diff --git a/locales/en_US.json b/locales/en_US.json new file mode 100644 index 0000000..70c8a6a --- /dev/null +++ b/locales/en_US.json @@ -0,0 +1,14 @@ +{ + "user_joined_call": "{member} joined the call! @everyone", + "user_in_channel": "{member} joined {channel}", + "user_left_channel": "{member} left {channel}", + "user_moved_to_channel": "{member} left {old_channel} and went to {new_channel}", + "leaderboard_title": "Entry Leaderboard:", + "leaderboard_entry": "{position}. {name}: {count} times", + "toggle_enabled": "Functionality to send a message when someone enters a call is now enabled.", + "toggle_disabled": "Functionality to send a message when someone enters a call is now disabled.", + "help_title": "Hello, {author}! Here's the list of available commands:", + "help_leaders": "/leaders - Shows how many times each user entered calls.", + "help_toggle": "/toggle - Turns on/off the functionality of sending a message when someone enters a call.", + "help_help": "/help - Get a list of commands." +} diff --git a/locales/pt_BR.json b/locales/pt_BR.json new file mode 100644 index 0000000..067d37a --- /dev/null +++ b/locales/pt_BR.json @@ -0,0 +1,14 @@ +{ + "user_joined_call": "{member} entrou na call! @everyone", + "user_in_channel": "{member} entrou em {channel}", + "user_left_channel": "{member} saiu de {channel}", + "user_moved_to_channel": "{member} saiu de {old_channel} e foi para {new_channel}", + "leaderboard_title": "Ranking de Entradas:", + "leaderboard_entry": "{position}. {name}: {count} vezes", + "toggle_enabled": "Funcionalidade de enviar mensagem quando alguém entra na call foi ativada.", + "toggle_disabled": "Funcionalidade de enviar mensagem quando alguém entra na call foi desativada.", + "help_title": "Olá, {author}! Aqui está a lista de comandos disponíveis:", + "help_leaders": "/leaders - Mostra quantas vezes cada usuário entrou em calls.", + "help_toggle": "/toggle - Ativa/desativa a funcionalidade de enviar mensagem quando alguém entra na call.", + "help_help": "/help - Obtém a lista de comandos." +}