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
This commit is contained in:
196
translator/logic.py
Normal file
196
translator/logic.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# -*- 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
|
||||
|
||||
Reference in New Issue
Block a user