Files
unstable-cogs/translator/logic.py
Unstable Kitsune 0e5afcb999 feat: add advanced translator cog with proxy and languages
Introduces multilingual translation cog supporting fantasy languages
Adds proxying features for role-playing with sonas and auto-translation
Includes admin commands for managing custom languages and context menus

Replaces basic translation with enhanced functionality for Discord bot
2025-09-22 23:23:57 -04:00

197 lines
7.9 KiB
Python

# -*- coding: utf-8 -*-
import re
import random
from redbot.core.utils.chat_formatting import box
def reverse_map(m): return {v: k for k, v in m.items()}
class TranslationLogicMixin:
"""This class holds all the translation logic for the cog."""
def _escape_regex(self, s):
return re.escape(s)
def _generic_translator(self, text, lang_map, char_separator):
# Regex to find custom emojis
emoji_regex = re.compile(r"<a?:\w+:\d+>")
# Find all emojis and store them
emojis = emoji_regex.findall(text)
# Replace emojis with a unique placeholder
placeholder = "||EMOJI||"
text_with_placeholders = emoji_regex.sub(placeholder, text)
# Translate the text with placeholders
word_separator = ' ' if char_separator == '' else ' '
words = text_with_placeholders.split(' ')
translated_words = []
emoji_counter = 0
for word in words:
if placeholder in word:
# If a word contains a placeholder, it might be part of the emoji code that got split.
# We simply re-insert the emoji from our list.
translated_words.append(emojis[emoji_counter])
emoji_counter += 1
else:
translated_word = char_separator.join([lang_map.get(char.lower(), char) for char in word])
translated_words.append(translated_word)
return word_separator.join(translated_words)
def _generic_decoder(self, text, reverse_map, chunk_size):
result = ""
text_no_space = text.replace(' ','')
for i in range(0, len(text_no_space), chunk_size):
chunk = text_no_space[i:i+chunk_size]
result += reverse_map.get(chunk, '?')
return result
def _greedy_decoder(self, text, reverse_map):
syllables = sorted(reverse_map.keys(), key=len, reverse=True)
regex = re.compile('|'.join(map(self._escape_regex, syllables)))
matches = regex.findall(text.replace(' ', ''))
return "".join([reverse_map.get(m, '?') for m in matches])
def _leet_decoder(self, text, reverse_map):
decoded_text = text
sorted_keys = sorted(reverse_map.keys(), key=len, reverse=True)
for key in sorted_keys:
decoded_text = decoded_text.replace(key, reverse_map[key])
return decoded_text
def _morse_decoder(self, text, reverse_map):
words = text.split(' ')
decoded_words = []
for word in words:
chars = word.split(' ')
decoded_words.append("".join([reverse_map.get(c, '?') for c in chars]))
return " ".join(decoded_words)
def _pig_latin_translator(self, text):
vowels = "aeiou"
translated_words = []
for word in text.split(' '):
if not word: continue
match = re.match(r"^([^a-zA-Z0-9]*)(.*?)([^a-zA-Z0-9]*)$", word)
leading_punct, clean_word, trailing_punct = match.groups()
if not clean_word:
translated_words.append(word)
continue
if clean_word and clean_word[0].lower() in vowels:
translated_word = clean_word + "way"
else:
first_vowel_index = -1
for i, char in enumerate(clean_word):
if char.lower() in vowels:
first_vowel_index = i
break
if first_vowel_index == -1:
translated_word = clean_word + "ay"
else:
translated_word = clean_word[first_vowel_index:] + clean_word[:first_vowel_index] + "ay"
translated_words.append(leading_punct + translated_word + trailing_punct)
return " ".join(translated_words)
def _pig_latin_decoder(self, text):
decoded_words = []
for word in text.split(' '):
if not word: continue
match = re.match(r"^([^a-zA-Z0-9]*)(.*?)([^a-zA-Z0-9]*)$", word)
leading_punct, clean_word, trailing_punct = match.groups()
if not clean_word:
decoded_words.append(word)
continue
original_word = ""
if clean_word.lower().endswith("way"):
original_word = clean_word[:-3]
elif clean_word.lower().endswith("ay"):
base_word = clean_word[:-2]
last_consonant_block_index = -1
for i in range(len(base_word) - 1, -1, -1):
if base_word[i].lower() not in "aeiou":
last_consonant_block_index = i
else:
break
if last_consonant_block_index != -1:
while last_consonant_block_index > 0 and base_word[last_consonant_block_index-1].lower() not in "aeiou":
last_consonant_block_index -= 1
consonants = base_word[last_consonant_block_index:]
stem = base_word[:last_consonant_block_index]
original_word = consonants + stem
else:
original_word = base_word
else:
original_word = clean_word
decoded_words.append(leading_punct + original_word + trailing_punct)
return " ".join(decoded_words)
def _uwu_translator(self, text):
text = text.lower()
text = text.replace('l', 'ww')
text = text.replace('r', 'w')
text = text.replace('na', 'nya').replace('ne', 'nye').replace('ni', 'nyi').replace('no', 'nyo').replace('nu', 'nyu')
text = text.replace('ove', 'uv')
words = text.split(' ')
uwu_words = []
for word in words:
if len(word) > 3 and random.random() < 0.3:
uwu_words.append(f"{word[0]}-{word}")
else:
uwu_words.append(word)
text = " ".join(uwu_words)
if random.random() < 0.5:
emoticons = [' uwu', ' owo', ' >w<', ' ^-^', ' ;;']
text += random.choice(emoticons)
return text
def _uwu_decoder(self, text):
emoticons = [' uwu', ' owo', ' >w<', ' ^-^', ' ;;']
for emo in emoticons:
if text.endswith(emo):
text = text[:-len(emo)]
text = re.sub(r'(\w)-(\w+)', r'\2', text)
text = text.replace('uv', 'ove')
text = text.replace('nyu', 'nu').replace('nyo', 'no').replace('nyi', 'ni').replace('nye', 'ne').replace('nya', 'na')
text = text.replace('ww', 'l')
text = text.replace('w', 'r')
return text
async def _find_language(self, query: str):
"""Finds languages by key or name, case-insensitively."""
query = query.lower()
exact_key_match = [key for key in self.all_languages if key.lower() == query]
if exact_key_match:
return exact_key_match
exact_name_match = [key for key, lang in self.all_languages.items() if lang['name'].lower() == query]
if exact_name_match:
return exact_name_match
partial_matches = [
key for key, lang in self.all_languages.items()
if query in key.lower() or query in lang['name'].lower()
]
return partial_matches
async def _auto_translate_to_common(self, text: str) -> list:
"""Helper to find all possible translations for a given text."""
possible_translations = []
for lang_key, lang_obj in self.all_languages.items():
if lang_key == 'common':
continue
try:
decoded_text = lang_obj['from_func'](text)
re_encoded_text = lang_obj['to_func'](decoded_text)
if re_encoded_text == text:
possible_translations.append(f"**As {lang_obj['name']}:**\n{box(decoded_text)}")
except Exception:
continue
return possible_translations