Categoría: Bots de Discord

Crear un bot de Discord#10¡Añade soporte multilingüe (i18n)! (ejemplo en Python)

Esta entrada es una traducción del original en coreano.

Índice10 secciones

¡Hola! Soy Mojangcam 😎

En el #9 conectamos la IA (API de Claude y ChatGPT) a nuestro bot y creamos un bot con el que puedes conversar, ¿verdad? En este episodio vamos a convertir nuestro bot en un bot global. ¡Se trata del soporte multilingüe (i18n)!

Si administras un servidor de juego con jugadores de otros países, un bot que solo responde en coreano se queda corto. Cada usuario de Discord tiene su propia configuración de idioma del cliente, y si la lees puedes crear un bot que responda automáticamente en coreano a los usuarios coreanos y en inglés a los extranjeros. ¡Incluso puedes mostrar el nombre y la descripción de los slash commands de forma distinta en cada idioma!

🧠 ¿Qué es i18n y por qué lo necesitas?

i18n es la abreviatura de internationalization (internacionalización). Se llama i18n porque entre la i y la n hay 18 letras 💡

En un bot de Discord, los momentos en los que necesitas i18n son más de los que crees.

  • Un bot de avisos para un servidor de juego (Minecraft, ARK, etc.) con jugadores de otros países
  • Un bot público que se instala en servidores de muchos países
  • Cuando quieres mostrar la descripción de los comandos en el idioma del usuario para que le cueste menos empezar

discord.py ya trae esto integrado. ¡Solo necesitas conocer dos cosas: interaction.locale (el idioma del usuario) y Translator (el traductor de comandos)!

📌 Lo que necesitas

  • Python 3.8 o superior
  • discord.py 2.x (este artículo usa la última versión, la 2.7.1)
  • El token del bot + un servidor de pruebas al que hayas invitado al bot (consulta el #1)
Terminal
pip install -U discord.py

📌 Paso 1 - Averigua el idioma del usuario (interaction.locale)

Cuando se ejecuta un slash command, Discord envía también la información de idioma de ese usuario. Primero veamos qué valor llega.

Python
@bot.tree.command(name="lang", description="Ver mi configuración de idioma")
async def lang(interaction: discord.Interaction):
    await interaction.response.send_message(
        f"Idioma del cliente: {interaction.locale}"
    )

Vamos a verlo parte por parte 💡

  • interaction.locale : es la configuración de idioma de la app de Discord del usuario que ejecutó el comando. Un cliente en coreano envía ko, uno en inglés (EE. UU.) envía en-US y uno en japonés envía ja.
  • interaction.guild_locale : este no es el idioma del usuario, sino el idioma predeterminado del servidor (guild). Viene bien para mensajes a nivel de servidor, como los anuncios.
  • El tipo que devuelve es un enum llamado discord.Locale, y si lo envuelves con str() se convierte en una cadena como 'ko'.

📌 Paso 2 - Cambia los mensajes de respuesta con un diccionario de traducciones

Ahora vamos a crear un diccionario de traducciones (dict) con la respuesta para cada idioma, y a elegir y enviar el mensaje que corresponda a interaction.locale.

Python
RESPONSES = {
    "ko": "안녕하세요, {user}님! 만나서 반가워요 🙌",
    "ja": "こんにちは、{user}さん!",
    "en-US": "Hello, {user}! Nice to meet you 🙌",
}
DEFAULT_LOCALE = "en-US"

@bot.tree.command(name="hello", description="Send a friendly greeting")
async def hello(interaction: discord.Interaction):
    lang = str(interaction.locale)
    template = RESPONSES.get(lang, RESPONSES[DEFAULT_LOCALE])
    await interaction.response.send_message(
        template.format(user=interaction.user.display_name)
    )

Vamos a verlo parte por parte 💡

  • RESPONSES : un diccionario de traducciones que usa los códigos de idioma como claves. Si quieres añadir un idioma, basta con agregar una línea aquí.
  • RESPONSES.get(lang, predeterminado) : si llega un idioma que no está en el diccionario (por ejemplo, el francés fr), el bot responde en el idioma predeterminado (inglés). ¡Es la red de seguridad clave del manejo multilingüe!
  • {user} + format() : un marcador de posición para insertar variables como el apodo dentro del mensaje.

📌 Paso 3 - Traduce también el nombre y la descripción de los comandos (Translator)

Cambiar solo las respuestas es quedarse a medias. ¿No sería mejor que los usuarios coreanos vieran el propio comando como /인사? Para eso sirve app_commands.Translator.

Python
from discord import app_commands
from discord.app_commands import locale_str as _T

COMMAND_I18N = {
    "ko": {
        "hello": "인사",
        "Send a friendly greeting": "봇이 반갑게 인사를 건네요",
    },
    "ja": {
        "hello": "あいさつ",
        "Send a friendly greeting": "ボットがあいさつします",
    },
}

class MyTranslator(app_commands.Translator):
    async def translate(self, string, locale, context):
        # Si hay traducción, devuelve el texto traducido; si no, None (se mantiene el original)
        return COMMAND_I18N.get(str(locale), {}).get(string.message)

Vamos a verlo parte por parte 💡

  • locale_str (_T) : un marcador que indica "esta cadena se debe traducir". Al crear un comando, la envuelves así: name=_T("hello").
  • Translator : cuando registras (sync) los comandos en el servidor, se llama a translate() para todos los idiomas que admite Discord. Si no hay traducción, devuelve None y se usa el texto original tal cual.
  • string.message : es la cadena original que envolviste con _T(). La usas como clave para buscar en el diccionario de traducciones.
  • ⚠️ Las reglas de los nombres de comandos también se aplican a las traducciones. ¡Nada de espacios, y las letras en inglés solo en minúscula! (el coreano y el japonés están bien tal cual)

Por último, registra el traductor antes de sync cuando arranque el bot.

Python
@bot.event
async def on_ready():
    await bot.tree.set_translator(MyTranslator())  # ¡Siempre antes de sync!
    await bot.tree.sync()
    print(f"¡{bot.user} ha iniciado sesión!")

📌 Código completo (puedes copiar y pegar)

Python
# discord.py 2.7.x / Python 3.8+
from typing import Optional

import discord
from discord import app_commands
from discord.app_commands import locale_str as _T
from discord.ext import commands

intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)

# Traducciones de nombres y descripciones de comandos (las usa Translator)
COMMAND_I18N = {
    "ko": {
        "hello": "인사",
        "Send a friendly greeting": "봇이 반갑게 인사를 건네요",
    },
    "ja": {
        "hello": "あいさつ",
        "Send a friendly greeting": "ボットがあいさつします",
    },
}

# Traducciones de los mensajes de respuesta (se eligen con interaction.locale)
RESPONSES = {
    "ko": "안녕하세요, {user}님! 만나서 반가워요 🙌",
    "ja": "こんにちは、{user}さん!",
    "en-US": "Hello, {user}! Nice to meet you 🙌",
}
DEFAULT_LOCALE = "en-US"

class MyTranslator(app_commands.Translator):
    async def translate(
        self,
        string: app_commands.locale_str,
        locale: discord.Locale,
        context: app_commands.TranslationContext,
    ) -> Optional[str]:
        return COMMAND_I18N.get(str(locale), {}).get(string.message)

@bot.tree.command(name=_T("hello"), description=_T("Send a friendly greeting"))
async def hello(interaction: discord.Interaction):
    lang = str(interaction.locale)  # p. ej.: 'ko', 'ja', 'en-US'
    template = RESPONSES.get(lang, RESPONSES[DEFAULT_LOCALE])
    await interaction.response.send_message(
        template.format(user=interaction.user.display_name)
    )

@bot.event
async def on_ready():
    await bot.tree.set_translator(MyTranslator())  # ¡Siempre antes de sync!
    await bot.tree.sync()
    print(f"¡{bot.user} ha iniciado sesión!")

bot.run("Pon aquí el token de tu bot")

📌 Resultado

Ejecuta el bot y prueba a escribir el comando...

  • Cliente en coreano: en la lista de comandos aparece como /인사 - 봇이 반갑게 인사를 건네요 ("/saludo - El bot te saluda con alegría"), y al ejecutarlo responde "안녕하세요, ○○님! 만나서 반가워요 🙌" ("¡Hola, ○○! Encantado de conocerte 🙌")
  • Cliente en inglés: aparece como /hello - Send a friendly greeting y responde "Hello, ○○! Nice to meet you 🙌"
  • Cliente en japonés: ¡aparece como /あいさつ! Es el mismo comando, pero se ve completamente distinto en cada idioma.

¡Prueba también a ejecutarlo en un idioma que no esté en el diccionario de traducciones (francés, etc.) y comprueba que responde en inglés, el idioma predeterminado!

📌 Errores frecuentes

  1. El nombre del comando no se traduce y solo aparece en inglés Causa: llamaste a set_translator() después de sync(), o no volviste a sincronizar después de registrar el traductor. Las traducciones se aplican en el momento del sync. Solución: respeta el orden await bot.tree.set_translator(...) → await bot.tree.sync() y recarga el cliente de Discord con Ctrl+R. Puede tardar unos minutos en reflejarse.
  2. Sale el error 400 Bad Request (CommandSyncFailure) durante el sync Causa: el nombre de comando traducido no cumple las reglas de Discord. Los nombres de comandos no pueden llevar espacios, y tampoco se permiten mayúsculas en inglés. Solución: revisa la parte de los nombres de comandos en el diccionario de traducciones. "인사 하기" (con espacio ❌) → "인사하기" (⭕), "Hello" (mayúscula ❌) → "hello" (⭕)
  3. interaction.locale siempre sale como en-US Causa: locale no se basa en el idioma del servidor, sino en la configuración de idioma de la app de Discord de cada usuario. Si tu app está en inglés, que salga en-US es lo normal. Solución: ve a Ajustes de Discord → Idioma, cámbialo a coreano y vuelve a probar. Si necesitas el idioma del servidor, usa interaction.guild_locale.

📌 Para terminar

Hoy creamos un bot multilingüe en 3 pasos: leer el idioma del usuario con interaction.locale, cambiar las respuestas con un diccionario de traducciones y traducir el nombre y la descripción de los comandos con Translator. Con solo añadir una línea por idioma al diccionario puedes admitir cualquier idioma, ¡así que amplíalo según lo que necesite tu servidor!

En el próximo episodio veremos cooldowns y gestión de permisos para evitar que se abuse de los comandos. ¡No te lo pierdas! 🙌

📚 Serie Crear un bot de Discord

  1. Crear un bot de Discord - ¡Domina por completo los Slash Commands!
  2. Crear un bot de Discord #2 - ¡Añade opciones (argumentos) a tus Slash Commands!
  3. Crear un bot de Discord #3 - ¡Añade subcomandos (Subcommand) a tus Slash Commands!
  4. Crear un bot de Discord #4 - ¡Envía embeds con Slash Commands!
  5. Crear un bot de Discord #5 - Implementa interacciones con botones (Button)
  6. Crear un bot de Discord #6 - Implementa menús desplegables (Select Menu)
  7. Crear un bot de Discord #7 - Pide texto al usuario con un modal (Modal)
  8. Crear un bot de Discord #8 - Decora embeds (Embed) con imágenes y campos
  9. Crear un bot de Discord #9 - Integra la IA (API de Claude y ChatGPT)
  10. Crear un bot de Discord #10 - Añade soporte multilingüe (i18n) ← ¡estás leyendo este!

Serie · Parte 10 de 32

Crear un bot de Discord

  1. #1Crear un bot de Discord - ¡Domina por completo los Slash Commands!
  2. #2¡Añade opciones (argumentos) a tus Slash Commands!
  3. #3¡Añade subcomandos (Subcommand) a tus Slash Commands!
  4. #4¡Envía embeds con Slash Commands!
  5. #5¡Implementa interacciones con botones (Button)!
  6. #6¡Implementa menús desplegables (Select Menu)!
  7. #7Pide texto al usuario con un modal (Modal) (ejemplo en Python)
  8. #8Decora embeds (Embed) con imágenes y campos (ejemplo en Python)
  9. #9¡Integra la IA (API de Claude y ChatGPT)! (ejemplo en Python)
  10. #10¡Añade soporte multilingüe (i18n)! (ejemplo en Python)Estás aquí
  11. #11¡Crea un panel web con Flask! (ejemplo en Python)
  12. #12¡Gestiona los cooldowns y permisos de tus comandos! (ejemplo en Python)
  13. #13¡Administra tu servidor automatizando roles y canales! (ejemplo en Python)
  14. #14¡Guarda tus datos! JSON vs SQLite (ejemplo en Python)
  15. #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제) (en coreano)
  16. #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제) (en coreano)
  17. #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제) (en coreano)
  18. #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제) (en coreano)
  19. #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제) (en coreano)
  20. #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제) (en coreano)
  21. #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제) (en coreano)
  22. #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제) (en coreano)
  23. #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제) (en coreano)
  24. #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제) (en coreano)
  25. #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제) (en coreano)
  26. #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제) (en coreano)
  27. #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제) (en coreano)
  28. #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제) (en coreano)
  29. #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제) (en coreano)
  30. #30¡Crea un bot de encuestas y votaciones (Poll)! Encuestas nativas + botones con recuento en tiempo real (ejemplo en Python) 🗳️
  31. #32¡Crea un canal de registros y auditoría (Audit Log) del servidor! Registra automáticamente mensajes borrados y editados, entradas, salidas y expulsiones (ejemplo en Python) 🕵️
  32. #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉 (en coreano)
Ver las 32Contraer

Autor

Mojangcam

Un registro práctico de desarrollo e IT: código que escribo, servidores que mantengo y los errores que resuelvo por el camino.