spun off the translation into its own thing that works with anything

This commit is contained in:
WhatDidYouExpect 2025-07-06 21:06:04 +02:00
parent cd6ce96b36
commit b89390e713
18 changed files with 248 additions and 80 deletions

2
.gitignore vendored
View file

@ -9,3 +9,5 @@ venv/
output.png
.vscode/
received_memory.json
translation_report.txt
translationcompleteness.py

View file

@ -25,3 +25,4 @@ by expect (requires goober version 0.11.8 or higher)
[LastFM](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/webserver.py)
by expect (no idea what version it needs i've only tried it on 1.0.3)
- you have to add LASTFM_USERNAME and LASTFM_API_KEY to your .env

View file

@ -1,4 +1,5 @@
{
"checks_disabled": "Checks are disabled!",
"unhandled_exception": "An unhandled exception occurred. Please report this issue on GitHub.",
"active_users:": "Active users:",
"spacy_initialized": "spaCy and spacytextblob are ready.",

View file

@ -1,4 +1,9 @@
{
"checks_disabled": "Tarkistukset on poistettu käytöstä!",
"active_users:": "Aktiiviset käyttäjät:",
"spacy_initialized": "spaCy ja spacytextblob ovat valmiita.",
"spacy_model_not_found": "spaCy mallia ei löytynyt! Ladataan se....`",
"unhandled_exception": "Käsittelemätön virhe tapahtui. Ilmoita tästä GitHubiin.",
"env_file_not_found": ".env faili ei leitud! Palun loo see vajalike muutujatega.",
"error_fetching_active_users": "Aktiivsete kasutajate hankimisel tekkis viga: {error}",
"error_sending_alive_ping": "Elusoleku ping'i saatmisel tekkis viga: {error}",

View file

@ -1,5 +1,16 @@
{
"active_users:": "Aktiiviset käyttäjät:",
"cog_fail2": "Moduulin lataaminen epäonnistui:",
"command_ran_s": "Info: {interaction.user} suoritti",
"error_fetching_active_users": "Aktiivisten käyttäjien hankkimisessa tapahtui ongelma: {error}",
"error_sending_alive_ping": "Pingin lähettäminen goober centraliin epäonnistui: {error}",
"goober_server_alert": "Viesti goober centralista!\n",
"loaded_cog2": "Ladattiin moduuli:",
"spacy_initialized": "spaCy ja spacytextblob ovat valmiita.",
"spacy_model_not_found": "spaCy mallia ei löytynyt! Ladataan se....`",
"checks_disabled": "Tarkistukset on poistettu käytöstä!",
"unhandled_exception": "Käsittelemätön virhe tapahtui. Ilmoita tästä GitHubissa.",
"active_users": "Aktiiviset käyttäjät",
"env_file_not_found": ".env-tiedostoa ei löytnyt! Luo tiedosto jossa on tarvittavat muuttujat",
"already_started": "Olen jo käynnistynyt! Ei päivitetä...",
"please_restart": "Käynnistä uudelleen, hölmö!",

View file

@ -1,4 +1,53 @@
{
"checks_disabled": "Les vérifications sont désactivées !",
"unhandled_exception": "Une exception non gérée est survenue. Merci de rapporter ce problème sur GitHub.",
"active_users:": "Utilisateurs actifs :",
"spacy_initialized": "spaCy et spacytextblob sont prêts.",
"spacy_model_not_found": "Le modèle spaCy est introuvable ! Téléchargement en cours...",
"env_file_not_found": "Le fichier .env est introuvable ! Créez-en un avec les variables nécessaires.",
"error_fetching_active_users": "Erreur lors de la récupération des utilisateurs actifs : {error}",
"error_sending_alive_ping": "Erreur lors de lenvoi du ping actif : {error}",
"already_started": "Jai déjà démarré ! Je ne me mets pas à jour...",
"please_restart": "Redémarre, stp !",
"local_ahead": "Local {remote}/{branch} est en avance ou à jour. Pas de mise à jour...",
"remote_ahead": "Remote {remote}/{branch} est en avance. Mise à jour en cours...",
"cant_find_local_version": "Je ne trouve pas la variable local_version ! Ou elle a été modifiée et ce nest pas un entier !",
"running_prestart_checks": "Exécution des vérifications préalables au démarrage...",
"continuing_in_seconds": "Reprise dans {seconds} secondes... Appuie sur une touche pour passer.",
"missing_requests_psutil": "requests et psutil manquants ! Installe-les avec pip : `pip install requests psutil`",
"requirements_not_found": "requirements.txt introuvable à {path}, a-t-il été modifié ?",
"warning_failed_parse_imports": "Avertissement : Échec du parsing des imports depuis {filename} : {error}",
"cogs_dir_not_found": "Répertoire des cogs introuvable à {path}, scan ignoré.",
"std_lib_local_skipped": "LIB STD / LOCAL {package} (vérification sautée)",
"ok_installed": "OK",
"missing_package": "MANQUANT",
"missing_package2": "nest pas installé",
"missing_packages_detected": "Packages manquants détectés :",
"telling_goober_central": "Envoi à goober central à {url}",
"failed_to_contact": "Impossible de contacter {url} : {error}",
"all_requirements_satisfied": "Toutes les dépendances sont satisfaites.",
"ping_to": "Ping vers {host} : {latency} ms",
"high_latency": "Latence élevée détectée ! Tu pourrais avoir des délais de réponse.",
"could_not_parse_latency": "Impossible danalyser la latence.",
"ping_failed": "Ping vers {host} échoué.",
"error_running_ping": "Erreur lors du ping : {error}",
"memory_usage": "Utilisation mémoire : {used} Go / {total} Go ({percent}%)",
"memory_above_90": "Usage mémoire au-dessus de 90% ({percent}%). Pense à libérer de la mémoire.",
"total_memory": "Mémoire totale : {total} Go",
"used_memory": "Mémoire utilisée : {used} Go",
"low_free_memory": "Mémoire libre faible détectée ! Seulement {free} Go disponibles.",
"measuring_cpu": "Mesure de lusage CPU par cœur...",
"core_usage": "Cœur {idx} : [{bar}] {usage}%",
"total_cpu_usage": "Usage total CPU : {usage}%",
"high_avg_cpu": "Moyenne CPU élevée : {usage}%",
"really_high_cpu": "Charge CPU vraiment élevée ! Le système pourrait ralentir ou planter.",
"memory_file": "Fichier mémoire : {size} Mo",
"memory_file_large": "Fichier mémoire de 1 Go ou plus, pense à le nettoyer pour libérer de lespace.",
"memory_file_corrupted": "Fichier mémoire corrompu ! Erreur JSON : {error}",
"consider_backup_memory": "Pense à sauvegarder et recréer le fichier mémoire.",
"memory_file_encoding": "Problèmes dencodage du fichier mémoire : {error}",
"error_reading_memory": "Erreur lecture fichier mémoire : {error}",
"memory_file_not_found": "Fichier mémoire introuvable.",
"modification_warning": "Goober a été modifié ! Toutes les modifications seront perdues lors d'une mise à jour !",
"reported_version": "Version rapportée :",
"current_hash": "Hachage actuel :",

View file

@ -1,7 +1,9 @@
{
"checks_disabled": "I controlli sono disabilitati!",
"unhandled_exception": "Si è verificata un'eccezione non gestita. Segnala questo problema su GitHub, per favore.",
"active_users:": "Utenti attivi:",
"spacy_initialized": "spaCy e spacytextblob sono pronti.",
"error_fetching_active_users": "Errore nel recupero degli utenti attivi: {error}",
"spacy_model_not_found": "Il modello spaCy non è stato trovato! Lo sto scaricando...",
"env_file_not_found": "Il file .env non è stato trovato! Crea un file con le variabili richieste.",
"error fetching_active_users": "Errore nel recupero degli utenti attivi:",
@ -108,6 +110,7 @@
"command_help_categories_admin": "Amministrazione",
"command_help_categories_custom": "Comandi personalizzati",
"command_ran": "Info: {message.author.name} ha eseguito {message.content}",
"command_ran_s": "Info: {interaction.user} ha eseguito ",
"command_desc_ping": "ping",
"command_ping_embed_desc": "Latenza del bot:",
"command_ping_footer": "Richiesto da",

4
bot.py
View file

@ -30,7 +30,7 @@ from better_profanity import profanity
from discord.ext import commands
from modules.central import ping_server
from modules.translations import _
from modules.volta.main import _
from modules.markovmemory import *
from modules.version import *
from modules.sentenceprocessing import *
@ -368,7 +368,7 @@ async def on_message(message: discord.Message) -> None:
# Process commands if message starts with a command prefix
if message.content.startswith((f"{PREFIX}talk", f"{PREFIX}mem", f"{PREFIX}help", f"{PREFIX}stats", f"{PREFIX}")):
print(f"{(_('failed_generate_image')).format(message=message)}")
print(f"{(_('command_ran')).format(message=message)}")
await bot.process_commands(message)
return

View file

@ -12,14 +12,15 @@ gooberTOKEN=
song="War Without Reason"
POSITIVE_GIFS="https://tenor.com/view/chill-guy-my-new-character-gif-2777893510283028272, https://tenor.com/view/goodnight-goodnight-friends-weezer-weezer-goodnight-gif-7322052181075806988"
splashtext="
d8b
?88
88b
d888b8b d8888b d8888b 888888b d8888b 88bd88b
d8P' ?88 d8P' ?88d8P' ?88 88P `?8bd8b_,dP 88P' `
88b ,88b 88b d8888b d88 d88, d8888b d88
`?88P'`88b`?8888P'`?8888P'd88'`?88P'`?888P'd88'
)88
,88P
`?8888P
SS\
SS |
SSSSSS\ SSSSSS\ SSSSSS\ SSSSSSS\ SSSSSS\ SSSSSS\
SS __SS\ SS __SS\ SS __SS\ SS __SS\ SS __SS\ SS __SS\
SS / SS |SS / SS |SS / SS |SS | SS |SSSSSSSS |SS | \__|
SS | SS |SS | SS |SS | SS |SS | SS |SS ____|SS |
\SSSSSSS |\SSSSSS |\SSSSSS |SSSSSSS |\SSSSSSS\ SS |
\____SS | \______/ \______/ \_______/ \_______|\__|
SS\ SS |
\SSSSSS |
\______/
"

View file

@ -1,7 +1,7 @@
import requests
import os
import modules.globalvars as gv
from modules.translations import _
from modules.volta.main import _
from modules.markovmemory import get_file_info
# Ping the server to check if it's alive and send some info

View file

@ -3,7 +3,7 @@ import json
import markovify
import pickle
from modules.globalvars import *
from modules.translations import _
from modules.volta.main import _
# Get file size and line count for a given file path
def get_file_info(file_path):

View file

@ -1,12 +1,16 @@
from modules.globalvars import *
from modules.translations import _
from modules.volta.main import _, get_translation, load_translations, set_language, translations
import time
import os
import sys
import subprocess
import sysconfig
import ast
import json
import re
import importlib.metadata
# import shutil
psutilavaliable = True
try:
@ -16,30 +20,56 @@ except ImportError:
psutilavaliable = False
print(RED, _('missing_requests_psutil'), RESET)
def check_missing_translations():
if LOCALE == "en":
print("Locale is English, skipping missing key check.")
return
load_translations()
import re
import importlib.metadata
en_keys = set(translations.get("en", {}).keys())
locale_keys = set(translations.get(LOCALE, {}).keys())
missing_keys = en_keys - locale_keys
total_keys = len(en_keys)
missing_count = len(missing_keys)
if missing_count > 0:
percent_missing = (missing_count / total_keys) * 100
print(f"{YELLOW}Warning: {missing_count}/{total_keys} keys missing in locale '{LOCALE}' ({percent_missing:.1f}%)!{RESET}")
for key in sorted(missing_keys):
print(f" - {key}")
time.sleep(5)
else:
print("All translation keys present for locale:", LOCALE)
def get_stdlib_modules():
stdlib_path = pathlib.Path(sysconfig.get_paths()['stdlib'])
modules = set()
if hasattr(sys, 'builtin_module_names'):
modules.update(sys.builtin_module_names)
for file in stdlib_path.glob('*.py'):
if file.stem != '__init__':
modules.add(file.stem)
for folder in stdlib_path.iterdir():
if folder.is_dir() and (folder / '__init__.py').exists():
modules.add(folder.name)
for file in stdlib_path.glob('*.*'):
if file.suffix in ('.so', '.pyd'):
modules.add(file.stem)
return modules
def check_requirements():
STD_LIB_MODULES = {
"os", "sys", "time", "ast", "asyncio", "re", "subprocess", "json",
"datetime", "threading", "math", "logging", "functools", "itertools",
"collections", "shutil", "socket", "types", "enum", "pathlib",
"inspect", "traceback", "platform", "typing", "warnings", "email",
"http", "urllib", "argparse", "io", "copy", "pickle", "gzip", "csv",
}
STD_LIB_MODULES = get_stdlib_modules()
PACKAGE_ALIASES = {
"discord": "discord.py",
"better_profanity": "better-profanity",
}
parent_dir = os.path.dirname(os.path.abspath(__file__))
requirements_path = os.path.join(parent_dir, '..', 'requirements.txt')
requirements_path = os.path.abspath(requirements_path)
requirements_path = os.path.abspath(os.path.join(parent_dir, '..', 'requirements.txt'))
if not os.path.exists(requirements_path):
print(f"{RED}{(_('requirements_not_found')).format(path=requirements_path)}{RESET}")
@ -52,7 +82,7 @@ def check_requirements():
if line.strip() and not line.startswith('#')
}
cogs_dir = os.path.abspath(os.path.join(parent_dir, '..', 'cogs'))
cogs_dir = os.path.abspath(os.path.join(parent_dir, '..', 'assets', 'cogs'))
if os.path.isdir(cogs_dir):
for filename in os.listdir(cogs_dir):
if filename.endswith('.py'):
@ -106,7 +136,7 @@ def check_requirements():
"token": gooberTOKEN
}
try:
response = requests.post(VERSION_URL + "/ping", json=payload)
requests.post(VERSION_URL + "/ping", json=payload)
except Exception as e:
print(f"{RED}{(_('failed_to_contact')).format(url=VERSION_URL, error=e)}{RESET}")
sys.exit(1)
@ -247,6 +277,7 @@ def start_checks():
print(f"{YELLOW}{(_('checks_disabled'))}{RESET}")
return
print(_('running_prestart_checks'))
check_missing_translations()
check_requirements()
check_latency()
check_memory()

View file

@ -1,6 +1,6 @@
import re
from modules.globalvars import *
from modules.translations import _
from modules.volta.main import _
import spacy
from spacy.tokens import Doc

View file

@ -1,31 +0,0 @@
import os
import json
import pathlib
from modules.globalvars import RED, RESET, LOCALE
# Load translations at module import
def load_translations():
translations = {}
translations_dir = pathlib.Path(__file__).parent.parent / 'assets' / 'locales'
for filename in os.listdir(translations_dir):
if filename.endswith(".json"):
lang_code = filename.replace(".json", "")
with open(translations_dir / filename, "r", encoding="utf-8") as f:
translations[lang_code] = json.load(f)
return translations
translations = load_translations()
def set_language(lang: str):
global LOCALE
LOCALE = lang if lang in translations else "en"
def get_translation(lang: str, key: str):
lang_translations = translations.get(lang, translations["en"])
if key not in lang_translations:
print(f"{RED}Missing key: {key} in language {lang}{RESET}")
return lang_translations.get(key, key)
def _(key: str) -> str:
return get_translation(LOCALE, key)

View file

@ -2,7 +2,7 @@ import sys
import traceback
import os
from modules.globalvars import RED, RESET, splashtext
from modules.translations import _
from modules.volta.main import _
def handle_exception(exc_type, exc_value, exc_traceback, *, context=None):
os.system('cls' if os.name == 'nt' else 'clear')

View file

@ -1,4 +1,4 @@
from modules.translations import _
from modules.volta.main import _
from modules.globalvars import *
import requests
import subprocess
@ -18,18 +18,18 @@ def is_remote_ahead(branch='main', remote='origin'):
# Automatically update the local repository if the remote is ahead
def auto_update(branch='main', remote='origin'):
if launched == True:
print((_('already_started')))
print(_("already_started"))
return
if AUTOUPDATE != "True":
pass # Auto-update is disabled
if is_remote_ahead(branch, remote):
print((_('remote_ahead')).format(remote=remote, branch=branch))
print(_( "remote_ahead").format(remote=remote, branch=branch))
pull_result = run_cmd(f'git pull {remote} {branch}')
print(pull_result)
print((_('please_restart')))
print(_( "please_restart"))
sys.exit(0)
else:
print((_('local_ahead')).format(remote=remote, branch=branch))
print(_( "local_ahead").format(remote=remote, branch=branch))
# Fetch the latest version info from the update server
def get_latest_version_info():
@ -38,23 +38,21 @@ def get_latest_version_info():
if response.status_code == 200:
return response.json()
else:
print(f"{RED}{(_('version_error'))} {response.status_code}{RESET}")
print(f"{RED}{_( 'version_error')} {response.status_code}{RESET}")
return None
except requests.RequestException as e:
print(f"{RED}{(_('version_error'))} {e}{RESET}")
print(f"{RED}{_( 'version_error')} {e}{RESET}")
return None
# Check if an update is available and perform update if needed
def check_for_update():
global latest_version, local_version, launched
launched = True
if ALIVEPING != "True":
return # Update check is disabled
return
global latest_version, local_version
latest_version_info = get_latest_version_info()
if not latest_version_info:
print(f"{(_('fetch_update_fail'))}")
print(f"{_('fetch_update_fail')}")
return None, None
latest_version = latest_version_info.get("version")
@ -62,20 +60,24 @@ def check_for_update():
download_url = latest_version_info.get("download_url")
if not latest_version or not download_url:
print(f"{RED}{(_('invalid_server'))}{RESET}")
print(f"{RED}{_(LOCALE, 'invalid_server')}{RESET}")
return None, None
# Check if local_version is valid
if local_version == "0.0.0" or None:
print(f"{RED}{(_('cant_find_local_version'))}{RESET}")
print(f"{RED}{_('cant_find_local_version')}{RESET}")
return
# Compare local and latest versions
if local_version < latest_version:
print(f"{YELLOW}{(_('new_version')).format(latest_version=latest_version, local_version=local_version)}{RESET}")
print(f"{YELLOW}{(_('changelog')).format(VERSION_URL=VERSION_URL)}{RESET}")
print(f"{YELLOW}{_('new_version').format(latest_version=latest_version, local_version=local_version)}{RESET}")
print(f"{YELLOW}{_('changelog').format(VERSION_URL=VERSION_URL)}{RESET}")
auto_update()
elif local_version > latest_version and beta == True:
print(f"{YELLOW}You are running an unstable version of Goober, do not expect it to work properly.\nVersion {local_version}{RESET}")
elif local_version > latest_version:
print(f"{YELLOW}{_('modification_warning')}{RESET}")
elif local_version == latest_version:
print(f"{GREEN}{(_('latest_version'))} {local_version}{RESET}")
print(f"{(_('latest_version2')).format(VERSION_URL=VERSION_URL)}\n\n")
print(f"{GREEN}{_('latest_version')} {local_version}{RESET}")
print(f"{_('latest_version2').format(VERSION_URL=VERSION_URL)}\n\n")
return latest_version

93
modules/volta/main.py Normal file
View file

@ -0,0 +1,93 @@
import os
import json
import pathlib
import threading
import time
from dotenv import load_dotenv
from modules.globalvars import RED, RESET
load_dotenv()
LOCALE = os.getenv("locale")
module_dir = pathlib.Path(__file__).parent.parent
working_dir = pathlib.Path.cwd()
EXCLUDE_DIRS = {'.git', '__pycache__'}
locales_dirs = []
def find_locales_dirs(base_path):
found = []
for root, dirs, files in os.walk(base_path):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
if 'locales' in dirs:
locales_path = pathlib.Path(root) / 'locales'
found.append(locales_path)
dirs.remove('locales')
return found
locales_dirs.extend(find_locales_dirs(module_dir))
if working_dir != module_dir:
locales_dirs.extend(find_locales_dirs(working_dir))
translations = {}
_file_mod_times = {}
def load_translations():
global translations, _file_mod_times
translations.clear()
_file_mod_times.clear()
for locales_dir in locales_dirs:
for filename in os.listdir(locales_dir):
if filename.endswith(".json"):
lang_code = filename[:-5]
file_path = locales_dir / filename
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
if lang_code not in translations:
translations[lang_code] = {}
translations[lang_code].update(data)
_file_mod_times[(lang_code, file_path)] = file_path.stat().st_mtime
except Exception as e:
print(f"{RED}Failed loading {file_path}: {e}{RESET}")
def reload_if_changed():
while True:
for (lang_code, file_path), last_mtime in list(_file_mod_times.items()):
try:
current_mtime = file_path.stat().st_mtime
if current_mtime != last_mtime:
print(f"{RED}Translation file changed: {file_path}, reloading...{RESET}")
load_translations()
break
except FileNotFoundError:
print(f"{RED}Translation file removed: {file_path}{RESET}")
_file_mod_times.pop((lang_code, file_path), None)
if lang_code in translations:
translations.pop(lang_code, None)
def set_language(lang: str):
global LOCALE
if lang in translations:
LOCALE = lang
else:
print(f"{RED}Language '{lang}' not found, defaulting to 'en'{RESET}")
LOCALE = "en"
def get_translation(lang: str, key: str):
lang_translations = translations.get(lang, {})
if key in lang_translations:
return lang_translations[key]
fallback = translations.get("en", {}).get(key, key)
print(f"{RED}Missing key: '{key}' in language '{lang}', falling back to: '{fallback}'{RESET}")
return fallback
def _(key: str) -> str:
return get_translation(LOCALE, key)
load_translations()
watchdog_thread = threading.Thread(target=reload_if_changed, daemon=True)
watchdog_thread.start()