From 184685fdd87bb81ef23d2ee333acfff6571dba4e Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Sun, 6 Jul 2025 21:14:47 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 2 + README.md | 25 +++++++++++++ example.py | 3 ++ locales/en.json | 3 ++ locales/it.json | 3 ++ volta/main.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 example.py create mode 100644 locales/en.json create mode 100644 locales/it.json create mode 100644 volta/main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d17870 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.env \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..783b723 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ + +How To Use + +Put your translation JSON files inside locales directories anywhere under your project or working directory. + +Each JSON file must be named as the language code, e.g., en.json, es.json. + +Make sure you have a .env file with a locale variable defining your default language code (eg locale=en). + +Start the loader + +Just import or run this script — it immediately: + +Loads all translation files it finds. + +Starts a background thread watching those files for any edits. + +Set language + +Call set_language("fr") to switch the active locale dynamically. + +Fetch translations + +Use _('some.key') anywhere to get the localized string. + diff --git a/example.py b/example.py new file mode 100644 index 0000000..e168ec3 --- /dev/null +++ b/example.py @@ -0,0 +1,3 @@ +from volta.main import _ + +print(_("hello_key")) \ No newline at end of file diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000..1fdcd8b --- /dev/null +++ b/locales/en.json @@ -0,0 +1,3 @@ +{ + "hello_key": "Hello World!" +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json new file mode 100644 index 0000000..12f91e0 --- /dev/null +++ b/locales/it.json @@ -0,0 +1,3 @@ +{ + "hello_key": "Ciao Mondo!" +} \ No newline at end of file diff --git a/volta/main.py b/volta/main.py new file mode 100644 index 0000000..272e0a1 --- /dev/null +++ b/volta/main.py @@ -0,0 +1,99 @@ +import os +import json +import pathlib +import threading +import time +from dotenv import load_dotenv + +ANSI = "\033[" +RED = f"{ANSI}31m" +GREEN = f"{ANSI}32m" +YELLOW = f"{ANSI}33m" +DEBUG = f"{ANSI}1;30m" +RESET = f"{ANSI}0m" + +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()