From a0b9a93bb12b095d05cdff6fd4ecf2581297c5f7 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 7 Mar 2026 01:38:55 -0500 Subject: [PATCH 1/5] [IMP] script todo: support security check python environment --- requirement/erplibre_require-ments.txt | 1 + script/todo/todo.py | 99 ++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/requirement/erplibre_require-ments.txt b/requirement/erplibre_require-ments.txt index 632dc47..48dd70d 100644 --- a/requirement/erplibre_require-ments.txt +++ b/requirement/erplibre_require-ments.txt @@ -31,6 +31,7 @@ sshconf cloudflare psutil mmg +pip-audit # For mobile pillow diff --git a/script/todo/todo.py b/script/todo/todo.py index addb391..54fb9cd 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -209,6 +209,7 @@ class TODO: [7] Process - Outils sur les exécutions [8] Config - Traitement du fichier de configuration [9] Réseau - Outil réseautique +[10] Sécurité - Audit de sécurité des dépendances [0] Retour """ while True: @@ -252,6 +253,10 @@ class TODO: status = self.prompt_execute_network() if status is not False: return + elif status == "10": + status = self.prompt_execute_security() + if status is not False: + return else: print("Commande non trouvée 🤖!") @@ -801,6 +806,100 @@ class TODO: single_source_erplibre=True, ) + def prompt_execute_security(self): + print("🤖 Audit de sécurité des dépendances!") + lst_choice = [ + { + "prompt_description": "pip-audit - Vérifier les vulnérabilités sur les environnements Python" + }, + ] + help_info = self.fill_help_info(lst_choice) + + while True: + status = click.prompt(help_info) + print() + if status == "0": + return False + elif status == "1": + self.execute_pip_audit() + else: + print("Commande non trouvée 🤖!") + + def execute_pip_audit(self): + lst_version, lst_version_installed, odoo_installed_version = ( + self.get_odoo_version() + ) + + # Build list of installed environments + dct_env = {} + key_i = 0 + for dct_version in lst_version[::-1]: + erplibre_version = dct_version.get("erplibre_version") + venv_path = f".venv.{erplibre_version}" + req_path = f"requirement/requirements.{erplibre_version}.txt" + odoo_version = f"odoo{dct_version.get('odoo_version')}" + + if not os.path.isdir(venv_path): + continue + + key_i += 1 + key_s = str(key_i) + label = f"{key_s}: {erplibre_version}" + if odoo_version == odoo_installed_version: + label += " - Actuel" + if dct_version.get("default"): + label += " - Défaut" + + dct_env[key_s] = { + "label": label, + "venv_path": venv_path, + "req_path": req_path, + "erplibre_version": erplibre_version, + } + + if not dct_env: + print( + "Aucun environnement installé trouvé. Installez" + " d'abord une version d'Odoo." + ) + return + + # Show selection menu + str_input = ( + "💬 Choisir un environnement pour l'audit :\n\t" + + "\n\t".join([v["label"] for v in dct_env.values()]) + + "\n\t0: Retour" + + "\nSélection : " + ) + env_input = "" + while env_input not in dct_env.keys() and env_input != "0": + if env_input: + print( + f"Erreur, impossible de comprendre la valeur" + f" '{env_input}'" + ) + env_input = input(str_input).strip() + + if env_input == "0": + return + + selected = dct_env[env_input] + venv_path = selected["venv_path"] + req_path = selected["req_path"] + + if not os.path.isfile(req_path): + print(f"Fichier de dépendances introuvable : {req_path}") + return + + # TODO support bash from parameter if open gnome-terminal + cmd = f"pip-audit -r {req_path} -l;bash" + print(f"Exécution : {cmd}") + self.execute.exec_command_live( + cmd, + source_erplibre=True, + single_source_erplibre=False, + ) + def generate_config(self, add_arg=None): # Repeating to get all item before get group cmd = ( From 45160289f98b81f75e589e64eb11c12a88a6c240 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 7 Mar 2026 02:30:59 -0500 Subject: [PATCH 2/5] [ADD] claude agent information --- CLAUDE.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++ doc/AGENT.INFO.md | 5 +++++ 2 files changed, 61 insertions(+) create mode 100644 doc/AGENT.INFO.md diff --git a/CLAUDE.md b/CLAUDE.md index b2b54d1..6c520dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -246,3 +246,59 @@ Plateformes supportées : Ubuntu 20.04-25.04, Debian 12, Arch Linux, macOS (pyen - Pour les commits : suivre le format `[TYPE] description` (ex: `[FIX]`, `[UPD]`, `[ADD]`, `[REM]`) - Pour la documentation : modifier les `.base.md`, jamais les `.md` ou `.fr.md` directement - Outil mmg disponible via `source .venv.erplibre/bin/activate && mmg` + +## Workflow Orchestration + +### 1. Plan Mode Default +- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions) +- If something goes sideways, STOP and re-plan immediately +- Don't keep pushing. +- Use plan mode for verification steps, not just building +- Write detailed specs upfront to reduce ambiguity + +### 2. Subagent Strategy +- Use subagents liberally to keep the main context window clean. +- Offload research, exploration, and parallel analysis to subagents +- For complex problems, throw more compute at it via subagents +- One task per subagent for focused execution + +### 3. Self-Improvement Loop +- Capture Lessons: Update AGENT.md with any change in approach the user has asked you to make. +- Write rules for yourself that prevent the same mistake +- Ruthlessly iterate on these lessons until the mistake rate drops. +- Review lessons at session start for relevant project + +### 4. Verification Before Done +- Never mark a task complete without proving it works +- Diff behavior between main and your changes when relevant +- Ask yourself, "Would a staff engineer approve this?" +- Run tests, check logs, and demonstrate correctness. + +### 5. Demand Elegance (Balanced) +- For non-trivial changes, pause and ask, "Is there a more elegant way?" +- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution" +- Skip this for simple, obvious fixes +- Don't over-engineer. +- Challenge your own work before presenting it + +### 6. Autonomous Bug Fixing +- When given a bug report, just fix it. Don't ask for hand-holding +- Point at logs, errors, and failing tests. +- Then resolve them. +- Zero context switching required from the user +- Go fix failing CI tests without being told how + +## Task Management + +1. **Plan First**: Write a plan to `tasks/todo.md` with checkable items. +2. **Verify Plan**: Check in before starting implementation. +3. **Track Progress**: Mark items complete as you go. +4. **Explain Changes**: High-level summary at each step. +5. **Document Results**: Add review section to `tasks/todo.md` +6. **Capture Lessons**: Update `tasks/lessons.md` after corrections + +## Core Principles + +- **Simplicity First**: Make every change as simple as possible. Impact minimal code. +- **No Laziness**: Find root causes. No temporary fixes. Senior developer standards. +- **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs. diff --git a/doc/AGENT.INFO.md b/doc/AGENT.INFO.md new file mode 100644 index 0000000..97d9d80 --- /dev/null +++ b/doc/AGENT.INFO.md @@ -0,0 +1,5 @@ +# Agent GPT information + +## How to build + +Source for Best practices and workflows to use with an AI agent on any project https://gist.github.com/CodeLeom/519e930f70895924f0d2b9862b95a10b From 6ece30e54b81f6bf23c465708ee9508181753eb4 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 7 Mar 2026 02:31:11 -0500 Subject: [PATCH 3/5] [UPD] ERPLibre version default odoo 18 --- conf/supported_version_erplibre.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conf/supported_version_erplibre.json b/conf/supported_version_erplibre.json index c3e634e..b606397 100644 --- a/conf/supported_version_erplibre.json +++ b/conf/supported_version_erplibre.json @@ -24,14 +24,12 @@ "odoo_version": "15.0", "python_version": "3.8.20", "poetry_version": "1.8.3", - "is_deprecated": true, "note": "Deprecated" }, "odoo16.0_python3.10.18": { "odoo_version": "16.0", "python_version": "3.10.18", "poetry_version": "1.8.3", - "default": true, "note": "Official support ERPLibre 1.6.0" }, "odoo17.0_python3.10.18": { @@ -44,6 +42,7 @@ "odoo_version": "18.0", "python_version": "3.12.10", "poetry_version": "2.1.3", + "default": true, "note": "Last version" } } From f1f57758a1a456d5b684590ad366e1a04aa03b31 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 7 Mar 2026 02:59:07 -0500 Subject: [PATCH 4/5] [IMP] i18n script todo --- CLAUDE.md | 25 ++- env_var.sh | 1 + script/todo/todo.json | 16 +- script/todo/todo.py | 281 +++++++++++++----------- script/todo/todo_i18n.py | 451 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 639 insertions(+), 135 deletions(-) create mode 100644 script/todo/todo_i18n.py diff --git a/CLAUDE.md b/CLAUDE.md index 6c520dc..b98adc8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -223,6 +223,29 @@ make doc_markdown # Regénérer toute la doc multilingue - `script/*/` : database, deployment, fork_github_repo, nginx, restful, selenium (2), todo, odoo/migration - `.github/ISSUE_TEMPLATE/` : bug_report, feature_request +## Internationalisation du CLI TODO (i18n) + +Le CLI interactif `script/todo/todo.py` supporte le français et l'anglais. + +### Architecture +- **`script/todo/todo_i18n.py`** — Module de traduction (dictionnaire `TRANSLATIONS`, fonctions `t()`, `get_lang()`, `set_lang()`) +- Les chaînes traduisibles utilisent `t("clé")` au lieu de texte en dur +- Les entrées de `todo.json` peuvent avoir un champ `prompt_description_key` résolu via `t()` (fallback sur `prompt_description`) + +### Résolution de la langue (priorité) +1. Variable d'environnement `EL_LANG` (définie dans `env_var.sh`, défaut `"fr"`) +2. Défaut : `"fr"` + +### Comportement +- Première exécution : prompt bilingue demande à l'utilisateur de choisir sa langue +- Le choix est persisté dans `env_var.sh` +- Changement de langue possible via le menu Execute > Langue/Language + +### Ajouter une traduction +1. Ajouter la clé dans `TRANSLATIONS` de `todo_i18n.py` avec les valeurs `"fr"` et `"en"` +2. Remplacer la chaîne en dur par `t("ma_clé")` dans `todo.py` +3. Pour les entrées JSON : ajouter `"prompt_description_key": "ma_clé"` dans `todo.json` + ## Déploiement - **Docker** : `docker-compose.yml` (PostgreSQL 18 + PostGIS 3.6) @@ -231,7 +254,7 @@ make doc_markdown # Regénérer toute la doc multilingue - **SSL** : Certbot pour les certificats - **DNS** : `script/deployment/update_dns_cloudflare.py` -Plateformes supportées : Ubuntu 20.04-25.04, Debian 12, Arch Linux, macOS (pyenv), Windows (WSL/Docker). +Plateformes supportées : Ubuntu 20.04-25.04, Linux Mint 22.3, Debian 12, Arch Linux, macOS (pyenv), Windows (WSL/Docker). ## Points d'attention pour Claude diff --git a/env_var.sh b/env_var.sh index 9f6bf06..b13134c 100755 --- a/env_var.sh +++ b/env_var.sh @@ -32,3 +32,4 @@ EL_WEBSITE_NAME="localhost" EL_GITHUB_TOKEN="" EL_MANIFEST_PROD="./default.xml" EL_MANIFEST_DEV="./manifest/git_manifest_erplibre.xml" +EL_LANG="fr" diff --git a/script/todo/todo.json b/script/todo/todo.json index b96e68a..ad9b89b 100644 --- a/script/todo/todo.json +++ b/script/todo/todo.json @@ -10,45 +10,45 @@ }, "instance": [ { - "prompt_description": "Test - Instance de base minimale", + "prompt_description_key": "json_instance_test", "makefile_cmd": "db_restore_erplibre_base_db_test", "database": "test" }, { - "prompt_description": "Ouvrir RobotLibre \uD83E\uDD16 minimal", + "prompt_description_key": "json_robot_minimal", "makefile_cmd": "robot_libre", "database": "robotlibre" }, { - "prompt_description": "Ouvrir RobotLibre \uD83E\uDD16 en activant la recherche", + "prompt_description_key": "json_robot_search", "makefile_cmd": "robot_libre_all", "database": "robotlibre" } ], "function": [ { - "prompt_description": "Ouvrir ERPLibre avec TODO \uD83E\uDD16", + "prompt_description_key": "json_open_erplibre_todo", "command": "./.venv.erplibre/bin/python ./script/selenium/web_login.py" } ], "update_from_makefile": [ { - "prompt_description": "Update all erplibre_base on database test", + "prompt_description_key": "json_update_erplibre_base_test", "makefile_cmd": "db_erplibre_base_db_test_update_all", "database": "test" } ], "code_from_makefile": [ { - "prompt_description": "Show code status", + "prompt_description_key": "json_show_code_status", "makefile_cmd": "repo_show_status" }, { - "prompt_description": "Stash all code", + "prompt_description_key": "json_stash_all_code", "makefile_cmd": "repo_do_stash" }, { - "prompt_description": "Format modified code", + "prompt_description_key": "json_format_modified_code", "makefile_cmd": "format" } ] diff --git a/script/todo/todo.py b/script/todo/todo.py index 54fb9cd..e04c86f 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -22,6 +22,7 @@ sys.path.append(new_path) from script.config import config_file from script.execute import execute +from script.todo.todo_i18n import get_lang, lang_is_configured, set_lang, t file_error_path = ".erplibre.error.txt" cst_venv_erplibre = ".venv.erplibre" @@ -40,7 +41,6 @@ STRINGS_FILE = os.path.join( ) GRADLE_FILE = os.path.join(MOBILE_HOME_PATH, ANDROID_DIR, "app/build.gradle") -from script.execute import execute try: import tkinter as tk @@ -63,7 +63,7 @@ except ModuleNotFoundError as e: CRASH_E = e if not ENABLE_CRASH: - print("Importation success!") + print(t("Importation success!")) logging.basicConfig( format=( @@ -88,17 +88,49 @@ class TODO: self.config_file = config_file.ConfigFile() self.execute = execute.Execute() + def _ask_language(self): + if not lang_is_configured(): + print() + print("Choisir la langue / Choose language:") + print("[1] Francais") + print("[2] English") + choice = "" + while choice not in ("1", "2"): + choice = input("Select / Choisir : ").strip() + if choice == "1": + set_lang("fr") + else: + set_lang("en") + + def _change_language(self): + print() + print(t("lang_prompt") + ":") + print(f"[1] {t('lang_french')}") + print(f"[2] {t('lang_english')}") + print(f"[0] {t('back')}") + choice = "" + while choice not in ("0", "1", "2"): + choice = input(t("selection")).strip() + if choice == "0": + return False + elif choice == "1": + set_lang("fr") + else: + set_lang("en") + print(t("lang_changed")) + def run(self): with open(self.config_file.get_logo_ascii_file_path()) as my_file: print(my_file.read()) - print("Ouverture de TODO en cours ...") - print("🤖 => Entre tes directives par son chiffre et fait Entrée!") - help_info = """Commande : -[1] Execute -[2] Install -[3] Question -[4] Fork - Ouvre TODO 🤖 dans une nouvelle tabulation -[0] Quitter + self._ask_language() + print(t("opening")) + print(f"🤖 {t('enter_directives')}") + help_info = f"""{t("command")} +[1] {t("menu_execute")} +[2] {t("menu_install")} +[3] {t("menu_question")} +[4] {t("menu_fork")} +[0] {t("menu_quit")} """ while True: try: @@ -132,7 +164,7 @@ class TODO: # elif status == "3" or status == "install": # print("install") else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) print(status) # manipuler() @@ -161,9 +193,7 @@ class TODO: ["kdbx", "password"] ) if not mot_de_passe_kdbx: - mot_de_passe_kdbx = getpass.getpass( - prompt="Entrez votre mot de passe : " - ) + mot_de_passe_kdbx = getpass.getpass(prompt=t("enter_password")) kp = PyKeePass(chemin_fichier_kdbx, password=mot_de_passe_kdbx) @@ -173,9 +203,9 @@ class TODO: def execute_prompt_ia(self): while True: - help_info = """Commande : -[0] Retour -Écrit moi ta question """ + help_info = f"""{t("command")} +[0] {t("back")} +{t("ia_prompt")}""" status = click.prompt(help_info) print() if status == "0": @@ -199,18 +229,19 @@ class TODO: print() def prompt_execute(self): - help_info = """Commande : -[1] Run - Exécuter et installer une instance -[2] Automatisation - Démonstration des fonctions développées -[3] Mise à jour - Update all developed staging source code -[4] Code - Outil pour développeur -[5] Doc - Recherche de documentation -[6] Database - Outils sur les bases de données -[7] Process - Outils sur les exécutions -[8] Config - Traitement du fichier de configuration -[9] Réseau - Outil réseautique -[10] Sécurité - Audit de sécurité des dépendances -[0] Retour + help_info = f"""{t("command")} +[1] {t("menu_run")} +[2] {t("menu_automation")} +[3] {t("menu_update")} +[4] {t("menu_code")} +[5] {t("menu_doc")} +[6] {t("menu_database")} +[7] {t("menu_process")} +[8] {t("menu_config")} +[9] {t("menu_network")} +[10] {t("menu_security")} +[11] {t("menu_lang")} +[0] {t("back")} """ while True: status = click.prompt(help_info) @@ -257,8 +288,12 @@ class TODO: status = self.prompt_execute_security() if status is not False: return + elif status == "11": + status = self._change_language() + if status is not False: + return else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_install(self): print("Detect first installation from code source.") @@ -338,7 +373,7 @@ class TODO: ), "0": ( "0", - "0: Quitter", + f"0: {t('menu_quit')}", ), } dct_final_cmd_intern = {} @@ -374,11 +409,11 @@ class TODO: odoo_version_input = "" while odoo_version_input not in dct_cmd_intern.keys(): if odoo_version_input: - print(f"Error, cannot understand value '{odoo_version_input}'") + print(f"{t('error_value')} '{odoo_version_input}'") str_input_dyn_odoo_version = ( - "💬 Choose a version:\n\t" + f"💬 {t('choose_version')}\n\t" + "\n\t".join([a[1] for a in dct_cmd_intern.values()]) - + "\nSelect : " + + f"\n{t('selection')}" ) odoo_version_input = ( input(str_input_dyn_odoo_version).strip().lower() @@ -388,7 +423,7 @@ class TODO: return cmd_intern = dct_cmd_intern.get(odoo_version_input)[2] - print(f"Will execute :\n{cmd_intern}") + print(f"{t('will_execute')}\n{cmd_intern}") # TODO use external script to detect terminal to use on system # TODO check script open_terminal_code_generator.sh @@ -398,9 +433,7 @@ class TODO: cmd_intern, shell=True, executable="/bin/bash", check=True ) except subprocess.CalledProcessError as e: - print( - f"Le script Bash «{cmd_intern}» a échoué avec le code de retour {e.returncode}." - ) + print(f"{t('script_failed')} {e.returncode}.") print("Wait after installation and open projects by terminal.") print("make open_terminal") self.restart_script(str(e)) @@ -453,12 +486,15 @@ class TODO: callback(dct_instance) def fill_help_info(self, lst_choice): - help_info = "Commande :\n" - help_end = "[0] Retour\n" + help_info = t("command") + "\n" + help_end = f"[0] {t('back')}\n" for i, dct_instance in enumerate(lst_choice): - help_info += ( - f"[{i + 1}] " + dct_instance["prompt_description"] + "\n" - ) + desc_key = dct_instance.get("prompt_description_key") + if desc_key: + desc = t(desc_key) + else: + desc = dct_instance["prompt_description"] + help_info += f"[{i + 1}] " + desc + "\n" help_info += help_end return help_info @@ -472,14 +508,14 @@ class TODO: # Support mobile ERPLibre if os.path.exists(MOBILE_HOME_PATH): dct_upgrade_odoo_database = { - "prompt_description": "Mobile - Compile and run software", + "prompt_description": t("mobile_compile_run"), "callback": self.callback_make_mobile_home, } lst_choice.append(dct_upgrade_odoo_database) # Support custom database to execute dct_upgrade_odoo_database = { - "prompt_description": "Choisir sa base de données", + "prompt_description": t("choose_database"), "callback": self.callback_execute_custom_database, } lst_choice.insert(0, dct_upgrade_odoo_database) @@ -496,9 +532,7 @@ class TODO: int_cmd = int(status) if 1 < int_cmd <= init_len: cmd_no_found = False - status = click.confirm( - "Voulez-vous une nouvelle instance?" - ) + status = click.confirm(t("new_instance_confirm")) dct_instance = lst_choice[int_cmd - 1] self.execute_from_configuration( dct_instance, @@ -515,7 +549,7 @@ class TODO: except ValueError: pass if cmd_no_found: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_fonction(self): lst_choice = self.config_file.get_config("function") @@ -537,11 +571,11 @@ class TODO: except ValueError: pass if cmd_no_found: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_update(self): # self.execute.exec_command_live(f"make {makefile_cmd}") - print("🤖 Mise à jour du développement") + print(f"🤖 {t('update_dev')}") # TODO détecter les modules en modification pour faire la mise à jour en cours # TODO demander sur quel BD faire la mise à jour # TODO proposer les modules manuelles selon la configuration à mettre à jour @@ -552,11 +586,11 @@ class TODO: lst_choice = self.config_file.get_config("update_from_makefile") dct_upgrade_odoo_database = { - "prompt_description": "Upgrade Odoo - Migration Database", + "prompt_description": t("upgrade_odoo_migration"), } lst_choice.append(dct_upgrade_odoo_database) dct_upgrade_poetry = { - "prompt_description": "Upgrade Poetry - Dependency of Odoo", + "prompt_description": t("upgrade_poetry_dependency"), } lst_choice.append(dct_upgrade_poetry) help_info = self.fill_help_info(lst_choice) @@ -582,10 +616,10 @@ class TODO: except ValueError: pass if cmd_no_found: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_code(self): - print("🤖 Qu'avez-vous de besoin pour développer?") + print(f"🤖 {t('code_need')}") # help_info = """Commande : # [1] Status Git local et distant # [2] Démarrer le générateur de code @@ -601,18 +635,18 @@ class TODO: lst_choice = self.config_file.get_config("code_from_makefile") dct_upgrade_odoo_database = { - "prompt_description": "Open SHELL", + "prompt_description": t("open_shell"), } lst_choice.append(dct_upgrade_odoo_database) dct_upgrade_odoo_database = { - "prompt_description": "Upgrade Module", + "prompt_description": t("upgrade_module"), } lst_choice.append(dct_upgrade_odoo_database) lst_choice.append( { - "prompt_description": "Debug", + "prompt_description": t("debug"), } ) @@ -640,15 +674,15 @@ class TODO: except ValueError: pass if cmd_no_found: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_doc(self): - print("🤖 Vous cherchez de la documentation?") + print(f"🤖 {t('doc_search')}") lst_choice = [ - {"prompt_description": "Migration module coverage"}, - {"prompt_description": "What change between version"}, - {"prompt_description": "OCA guidelines"}, - {"prompt_description": "OCA migration Odoo 19 milestone"}, + {"prompt_description": t("migration_module_coverage")}, + {"prompt_description": t("what_change_between_version")}, + {"prompt_description": t("oca_guidelines")}, + {"prompt_description": t("oca_migration_odoo_19")}, ] help_info = self.fill_help_info(lst_choice) @@ -689,16 +723,14 @@ class TODO: elif status == "4": print("https://github.com/OCA/maintainer-tools/issues/658") else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_database(self): - print("🤖 Faites des modifications sur les bases de données!") + print(f"🤖 {t('db_modify')}") lst_choice = [ - { - "prompt_description": "Download database to create backup (.zip)" - }, - {"prompt_description": "Restore from backup (.zip)"}, - {"prompt_description": "Create backup (.zip)"}, + {"prompt_description": t("download_db_backup")}, + {"prompt_description": t("restore_from_backup")}, + {"prompt_description": t("create_backup")}, ] help_info = self.fill_help_info(lst_choice) @@ -714,12 +746,12 @@ class TODO: elif status == "3": self.create_backup_from_database() else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_process(self): - print("🤖 Manipuler les processus d'exécution!") + print(f"🤖 {t('process_manage')}") lst_choice = [ - {"prompt_description": "Kill process from actual port"}, + {"prompt_description": t("kill_process_port")}, ] help_info = self.fill_help_info(lst_choice) @@ -731,16 +763,16 @@ class TODO: elif status == "1": self.process_kill_from_port() else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_config(self): - print("🤖 Manipuler la configuration ERPLibre et Odoo!") + print(f"🤖 {t('config_manage')}") lst_choice = [ - {"prompt_description": "Generate all configuration"}, - {"prompt_description": "Generate from pre-configuration"}, - {"prompt_description": "Generate from backup file"}, - {"prompt_description": "Generate from database"}, - {"prompt_description": "Setup queue job for parallelism"}, + {"prompt_description": t("generate_all_config")}, + {"prompt_description": t("generate_from_preconfig")}, + {"prompt_description": t("generate_from_backup")}, + {"prompt_description": t("generate_from_database")}, + {"prompt_description": t("setup_queue_job_for_parallelism")}, ] help_info = self.fill_help_info(lst_choice) @@ -760,13 +792,17 @@ class TODO: elif status == "5": self.generate_config_queue_job() else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def prompt_execute_network(self): - print("🤖 Outil réseautique!") + print(f"🤖 {t('network_tools')}") lst_choice = [ - {"prompt_description": "SSH port-forwarding"}, - {"prompt_description": "Network performance request per second"}, + {"prompt_description": t("ssh_port_forwarding")}, + { + "prompt_description": t( + "network_performance_request_per_second" + ) + }, ] help_info = self.fill_help_info(lst_choice) @@ -780,7 +816,7 @@ class TODO: elif status == "2": self.generate_network_performance_test() else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def generate_network_port_forwarding(self, add_arg=None): # ssh -L local_port:localhost:remote_port SSH_connection @@ -807,11 +843,9 @@ class TODO: ) def prompt_execute_security(self): - print("🤖 Audit de sécurité des dépendances!") + print(f"🤖 {t('security_audit')}") lst_choice = [ - { - "prompt_description": "pip-audit - Vérifier les vulnérabilités sur les environnements Python" - }, + {"prompt_description": t("pip_audit_desc")}, ] help_info = self.fill_help_info(lst_choice) @@ -823,7 +857,7 @@ class TODO: elif status == "1": self.execute_pip_audit() else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def execute_pip_audit(self): lst_version, lst_version_installed, odoo_installed_version = ( @@ -846,9 +880,9 @@ class TODO: key_s = str(key_i) label = f"{key_s}: {erplibre_version}" if odoo_version == odoo_installed_version: - label += " - Actuel" + label += f" - {t('current')}" if dct_version.get("default"): - label += " - Défaut" + label += f" - {t('default')}" dct_env[key_s] = { "label": label, @@ -858,26 +892,20 @@ class TODO: } if not dct_env: - print( - "Aucun environnement installé trouvé. Installez" - " d'abord une version d'Odoo." - ) + print(t("no_env_installed")) return # Show selection menu str_input = ( - "💬 Choisir un environnement pour l'audit :\n\t" + f"💬 {t('choose_env_audit')}\n\t" + "\n\t".join([v["label"] for v in dct_env.values()]) - + "\n\t0: Retour" - + "\nSélection : " + + f"\n\t0: {t('back')}" + + f"\n{t('selection')}" ) env_input = "" while env_input not in dct_env.keys() and env_input != "0": if env_input: - print( - f"Erreur, impossible de comprendre la valeur" - f" '{env_input}'" - ) + print(f"{t('error_value')}" f" '{env_input}'") env_input = input(str_input).strip() if env_input == "0": @@ -888,12 +916,12 @@ class TODO: req_path = selected["req_path"] if not os.path.isfile(req_path): - print(f"Fichier de dépendances introuvable : {req_path}") + print(f"{t('dep_file_not_found')}{req_path}") return # TODO support bash from parameter if open gnome-terminal cmd = f"pip-audit -r {req_path} -l;bash" - print(f"Exécution : {cmd}") + print(f"{t('execution')}{cmd}") self.execute.exec_command_live( cmd, source_erplibre=True, @@ -920,10 +948,10 @@ class TODO: def generate_config_from_preconfiguration(self): lst_choice = [ - {"prompt_description": "base"}, - {"prompt_description": "base + code_generator"}, - {"prompt_description": "base + image_db"}, - {"prompt_description": "all"}, + {"prompt_description": t("preconfig_base")}, + {"prompt_description": t("preconfig_base_code_generator")}, + {"prompt_description": t("preconfig_base_image_db")}, + {"prompt_description": t("preconfig_all")}, # {"prompt_description": "base + migration"}, ] help_info = self.fill_help_info(lst_choice) @@ -952,11 +980,11 @@ class TODO: # str_group = f"--group {group}" # self.generate_config(add_arg=str_group) else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def debug_ide(self): lst_choice = [ - {"prompt_description": "Debug todo.py"}, + {"prompt_description": t("debug_todo_py")}, ] help_info = self.fill_help_info(lst_choice) @@ -971,7 +999,7 @@ class TODO: os.path.join(os.getcwd(), "script/todo/todo.py"), ) else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def generate_config_from_backup(self): file_name = self.open_file_image_db() @@ -1016,7 +1044,7 @@ class TODO: print(database_name) return database_name else: - print("Commande non trouvée 🤖!") + print(t("cmd_not_found")) def get_odoo_version(self): with open(VERSION_DATA_FILE) as txt: @@ -1175,14 +1203,15 @@ class TODO: def open_shell_on_database(self): database = self.select_database() - cmd_server = f"./odoo_bin.sh shell -d {database}" - status, lst_database = self.execute.exec_command_live( - cmd_server, - return_status_and_output=True, - source_erplibre=False, - single_source_erplibre=True, - new_window=True, - ) + if database: + cmd_server = f"./odoo_bin.sh shell -d {database}" + status, lst_database = self.execute.exec_command_live( + cmd_server, + return_status_and_output=True, + source_erplibre=False, + single_source_erplibre=True, + new_window=True, + ) def open_pycharm_file(self, folder, filename): cmd = "~/.local/share/JetBrains/Toolbox/scripts/pycharm" @@ -1411,7 +1440,7 @@ class TODO: return status, output_path, database_name def restart_script(self, last_error): - print("Reboot TODO 🤖...") + print(f"🤖 {t('reboot_todo')}") # os.execv(sys.executable, ['python'] + sys.argv) # TODO mettre check que le répertoire est créé, s'il existe, auto-loop à corriger if os.path.exists(cst_venv_erplibre) and not os.path.exists( @@ -1579,13 +1608,13 @@ if __name__ == "__main__": todo.crash_diagnostic(CRASH_E) todo.run() except KeyboardInterrupt: - print("Keyboard interrupt") + print(t("keyboard_interrupt")) finally: end_time = time.time() duration_sec = end_time - start_time if humanize: duration_delta = datetime.timedelta(seconds=duration_sec) humain_time = humanize.precisedelta(duration_delta) - print(f"\nTODO execution time {humain_time}\n") + print(f"\n{t('execution_time')} {humain_time}\n") else: - print(f"\nTODO execution time {duration_sec:.2f} sec.\n") + print(f"\n{t('execution_time')} {duration_sec:.2f} sec.\n") diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py new file mode 100644 index 0000000..8a84067 --- /dev/null +++ b/script/todo/todo_i18n.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python3 +# © 2021-2025 TechnoLibre (http://www.technolibre.ca) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import os +import re + +CONFIG_OVERRIDE_PRIVATE_FILE = "./env_var.sh" + +_current_lang = None + +TRANSLATIONS = { + # Main menu + "Importation success!": { + "fr": "L'importation est un succès!", + "en": "Importation success!", + }, + "opening": { + "fr": "Ouverture de TODO en cours ...", + "en": "Opening TODO ...", + }, + "enter_directives": { + "fr": "=> Entre tes directives par son chiffre et fait Entrée!", + "en": "=> Enter your choice by number and press Enter!", + }, + "command": { + "fr": "Commande :", + "en": "Command:", + }, + "menu_execute": { + "fr": "Exécution", + "en": "Execute", + }, + "menu_install": { + "fr": "Installation", + "en": "Install", + }, + "menu_question": { + "fr": "Question", + "en": "Question", + }, + "menu_fork": { + "fr": "Fork - Ouvre TODO dans une nouvelle tabulation", + "en": "Fork - Open TODO in a new tab", + }, + "menu_quit": { + "fr": "Quitter", + "en": "Quit", + }, + "cmd_not_found": { + "fr": "Commande non trouvée !", + "en": "Command not found !", + }, + "back": { + "fr": "Retour", + "en": "Back", + }, + # Execute submenu + "menu_run": { + "fr": "Run - Exécuter et installer une instance", + "en": "Run - Execute and install an instance", + }, + "menu_automation": { + "fr": "Automatisation - Demonstration des fonctions développées", + "en": "Automation - Demonstration of developed features", + }, + "menu_update": { + "fr": "Mise à jour - Update all developed staging source code", + "en": "Update - Update all developed staging source code", + }, + "menu_code": { + "fr": "Code - Outil pour développeur", + "en": "Code - Developer tools", + }, + "menu_doc": { + "fr": "Doc - Recherche de documentation", + "en": "Doc - Documentation search", + }, + "menu_database": { + "fr": "Database - Outils sur les bases de données", + "en": "Database - Database tools", + }, + "menu_process": { + "fr": "Process - Outils sur les executions", + "en": "Process - Execution tools", + }, + "menu_config": { + "fr": "Config - Traitement du fichier de configuration", + "en": "Config - Configuration file management", + }, + "menu_network": { + "fr": "Réseau - Outil réseautique", + "en": "Network - Network tools", + }, + "menu_security": { + "fr": "Sécurité - Audit de sécurité des dépendances", + "en": "Security - Dependency security audit", + }, + "menu_lang": { + "fr": "Langue - Changer la langue / Change language", + "en": "Language - Change language / Changer la langue", + }, + # Prompts and messages + "enter_password": { + "fr": "Entrez votre mot de passe : ", + "en": "Enter your password: ", + }, + "ia_prompt": { + "fr": "Écrit moi ta question ", + "en": "Write your question ", + }, + "new_instance_confirm": { + "fr": "Voulez-vous une nouvelle instance?", + "en": "Do you want a new instance?", + }, + "ssh_port_forwarding": { + "fr": "SSH port-forwarding", + "en": "SSH port-forwarding", + }, + "network_performance_request_per_second": { + "fr": "Performance réseau en requêtes par seconde", + "en": "Network performance request per second", + }, + "setup_queue_job_for_parallelism": { + "fr": "Configurer la file d'attente pour l'exécution parallèle", + "en": "Setup queue job for parallelism", + }, + "choose_database": { + "fr": "Choisir sa base de données", + "en": "Choose your database", + }, + "update_dev": { + "fr": "Mise à jour du développement", + "en": "Development update", + }, + "code_need": { + "fr": "Qu'avez-vous de besoin pour développer?", + "en": "What do you need for development?", + }, + "doc_search": { + "fr": "Vous cherchez de la documentation?", + "en": "Looking for documentation?", + }, + "db_modify": { + "fr": "Faites des modifications sur les bases de données!", + "en": "Make changes to databases!", + }, + "process_manage": { + "fr": "Manipuler les processus d'exécution!", + "en": "Manage execution processes!", + }, + "config_manage": { + "fr": "Manipuler la configuration ERPLibre et Odoo!", + "en": "Manage ERPLibre and Odoo configuration!", + }, + "network_tools": { + "fr": "Outil réseautique!", + "en": "Network tools!", + }, + "security_audit": { + "fr": "Audit de securite des dépendances!", + "en": "Dependency security audit!", + }, + "script_failed": { + "fr": "Le script Bash a échoué avec le code de retour", + "en": "The Bash script failed with return code", + }, + "no_env_installed": { + "fr": "Aucun environnement installe trouve. Installez d'abord une version d'Odoo.", + "en": "No installed environment found. Install an Odoo version first.", + }, + "choose_env_audit": { + "fr": "Choisir un environnement pour l'audit :", + "en": "Choose an environment for the audit:", + }, + "selection": { + "fr": "Sélection : ", + "en": "Select: ", + }, + "error_value": { + "fr": "Erreur, impossible de comprendre la valeur", + "en": "Error, cannot understand value", + }, + "dep_file_not_found": { + "fr": "Fichier de dépendances introuvable : ", + "en": "Dependencies file not found: ", + }, + "execution": { + "fr": "Execution : ", + "en": "Execution: ", + }, + "current": { + "fr": "Actuel", + "en": "Current", + }, + "default": { + "fr": "Défaut", + "en": "Default", + }, + "reboot_todo": { + "fr": "Reboot TODO ...", + "en": "Reboot TODO ...", + }, + "pip_audit_desc": { + "fr": "pip-audit - Verifier les vulnérabilités sur les environnements Python", + "en": "pip-audit - Check vulnerabilities on Python environments", + }, + "will_execute": { + "fr": "Va exécuter :", + "en": "Will execute:", + }, + "choose_version": { + "fr": "Choisir une version :", + "en": "Choose a version:", + }, + "error_cannot_understand": { + "fr": "Erreur, impossible de comprendre la valeur", + "en": "Error, cannot understand value", + }, + # todo.json translatable prompt_descriptions + "json_instance_test": { + "fr": "Test - Instance de base minimale", + "en": "Test - Minimal base instance", + }, + "json_robot_minimal": { + "fr": "Ouvrir RobotLibre 🤖 minimal", + "en": "Open RobotLibre 🤖 minimal", + }, + "json_robot_search": { + "fr": "Ouvrir RobotLibre 🤖 en activant la recherche", + "en": "Open RobotLibre 🤖 with search enabled", + }, + "json_open_erplibre_todo": { + "fr": "Ouvrir ERPLibre avec TODO 🤖", + "en": "Open ERPLibre with TODO 🤖", + }, + "json_update_erplibre_base_test": { + "fr": "Mise à jour de tous les erplibre_base sur la base de données test", + "en": "Update all erplibre_base on database test", + }, + "json_show_code_status": { + "fr": "Afficher le statut du code", + "en": "Show code status", + }, + "json_stash_all_code": { + "fr": "Remiser tout le code", + "en": "Stash all code", + }, + "json_format_modified_code": { + "fr": "Formater le code modifié", + "en": "Format modified code", + }, + # todo.py hardcoded prompt_descriptions + "mobile_compile_run": { + "fr": "Mobile - Compiler et exécuter le logiciel", + "en": "Mobile - Compile and run software", + }, + "upgrade_odoo_migration": { + "fr": "Mise à jour Odoo - Migration de base de données", + "en": "Upgrade Odoo - Migration Database", + }, + "upgrade_poetry_dependency": { + "fr": "Mise à jour Poetry - Dépendances d'Odoo", + "en": "Upgrade Poetry - Dependency of Odoo", + }, + "open_shell": { + "fr": "Ouvrir le SHELL", + "en": "Open SHELL", + }, + "upgrade_module": { + "fr": "Mise à jour de module", + "en": "Upgrade Module", + }, + "debug": { + "fr": "Débogage", + "en": "Debug", + }, + "migration_module_coverage": { + "fr": "Couverture de migration des modules", + "en": "Migration module coverage", + }, + "what_change_between_version": { + "fr": "Quels changements entre les versions", + "en": "What change between version", + }, + "oca_guidelines": { + "fr": "Directives OCA", + "en": "OCA guidelines", + }, + "oca_migration_odoo_19": { + "fr": "Migration OCA Odoo 19 - Jalons", + "en": "OCA migration Odoo 19 milestone", + }, + "download_db_backup": { + "fr": "Télécharger une base de données pour créer une sauvegarde (.zip)", + "en": "Download database to create backup (.zip)", + }, + "restore_from_backup": { + "fr": "Restaurer a partir d'une sauvegarde (.zip)", + "en": "Restore from backup (.zip)", + }, + "create_backup": { + "fr": "Créer une sauvegarde (.zip)", + "en": "Create backup (.zip)", + }, + "kill_process_port": { + "fr": "Terminer le processus du port actuel", + "en": "Kill process from actual port", + }, + "generate_all_config": { + "fr": "Générer toute la configuration", + "en": "Generate all configuration", + }, + "generate_from_preconfig": { + "fr": "Générer a partir de la pre-configuration", + "en": "Generate from pre-configuration", + }, + "generate_from_backup": { + "fr": "Générer a partir d'un fichier de sauvegarde", + "en": "Generate from backup file", + }, + "generate_from_database": { + "fr": "Générer a partir de la base de données", + "en": "Generate from database", + }, + "preconfig_base": { + "fr": "base", + "en": "base", + }, + "preconfig_base_code_generator": { + "fr": "base + code_generator", + "en": "base + code_generator", + }, + "preconfig_base_image_db": { + "fr": "base + image_db", + "en": "base + image_db", + }, + "preconfig_all": { + "fr": "tout", + "en": "all", + }, + "debug_todo_py": { + "fr": "Débogage todo.py", + "en": "Debug todo.py", + }, + # Language selection + "lang_prompt": { + "fr": "Choisir la langue / Choose language", + "en": "Choose language / Choisir la langue", + }, + "lang_french": { + "fr": "Francais", + "en": "French", + }, + "lang_english": { + "fr": "Anglais", + "en": "English", + }, + "lang_changed": { + "fr": "Langue changée pour : Francais", + "en": "Language changed to: English", + }, + "execution_time": { + "fr": "Temps d'exécution TODO", + "en": "TODO execution time", + }, + "keyboard_interrupt": { + "fr": "Interruption clavier", + "en": "Keyboard interrupt", + }, +} + + +def get_lang(): + global _current_lang + if _current_lang is not None: + return _current_lang + + # 1. Check env_var.sh file + if os.path.exists(CONFIG_OVERRIDE_PRIVATE_FILE): + try: + with open(CONFIG_OVERRIDE_PRIVATE_FILE) as f: + content = f.read() + match = re.search( + r'^EL_LANG=["\']?(\w+)["\']?', content, re.MULTILINE + ) + if match: + lang = match.group(1) + if lang in ("fr", "en"): + _current_lang = lang + return _current_lang + except OSError: + pass + + # 2. Check env var + env_lang = os.environ.get("EL_LANG") + if env_lang in ("fr", "en"): + _current_lang = env_lang + return _current_lang + + # 3. Default + _current_lang = "fr" + return _current_lang + + +def set_lang(lang): + global _current_lang + _current_lang = lang + + # Persist to env_var.sh + if os.path.exists(CONFIG_OVERRIDE_PRIVATE_FILE): + try: + with open(CONFIG_OVERRIDE_PRIVATE_FILE) as f: + content = f.read() + except OSError: + return + + new_line = f'EL_LANG="{lang}"' + if re.search(r"^EL_LANG=", content, re.MULTILINE): + content = re.sub( + r'^EL_LANG=["\']?\w*["\']?', + new_line, + content, + count=1, + flags=re.MULTILINE, + ) + else: + content = content.rstrip("\n") + "\n" + new_line + "\n" + + with open(CONFIG_OVERRIDE_PRIVATE_FILE, "w") as f: + f.write(content) + + +def lang_is_configured(): + """Check if a language has been explicitly set.""" + if os.path.exists(CONFIG_OVERRIDE_PRIVATE_FILE): + try: + with open(CONFIG_OVERRIDE_PRIVATE_FILE) as f: + content = f.read() + return bool(re.search(r"^EL_LANG=", content, re.MULTILINE)) + except OSError: + pass + return False + + +def t(key): + entry = TRANSLATIONS.get(key) + if entry is None: + return key + lang = get_lang() + return entry.get(lang, entry.get("fr", key)) From 6fe4fd0676d71d88f8a8c726f046b4aa5438de70 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sun, 8 Mar 2026 15:53:51 -0400 Subject: [PATCH 5/5] [IMP] script TODO support running test --- script/todo/todo.py | 119 ++++++++++++++++++++++++++++++++++++++- script/todo/todo_i18n.py | 69 +++++++++++++++++++++++ 2 files changed, 187 insertions(+), 1 deletion(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index e04c86f..325171b 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -240,7 +240,8 @@ class TODO: [8] {t("menu_config")} [9] {t("menu_network")} [10] {t("menu_security")} -[11] {t("menu_lang")} +[11] {t("menu_test")} +[12] {t("menu_lang")} [0] {t("back")} """ while True: @@ -289,6 +290,10 @@ class TODO: if status is not False: return elif status == "11": + status = self.prompt_execute_test() + if status is not False: + return + elif status == "12": status = self._change_language() if status is not False: return @@ -859,6 +864,118 @@ class TODO: else: print(t("cmd_not_found")) + def prompt_execute_test(self): + print(f"🤖 {t('test_description')}") + lst_choice = [ + {"prompt_description": t("test_run_module")}, + {"prompt_description": t("test_run_module_coverage")}, + ] + help_info = self.fill_help_info(lst_choice) + + while True: + status = click.prompt(help_info) + print() + if status == "0": + return False + elif status == "1": + self.execute_test_module(coverage=False) + elif status == "2": + self.execute_test_module(coverage=True) + else: + print(t("cmd_not_found")) + + def execute_test_module(self, coverage=False): + # Module name + module_name = input(t("test_enter_module_name")).strip() + if not module_name: + print(t("test_module_required")) + return + + # Database name + db_name = input(t("test_db_name")).strip() + if not db_name: + db_name = "test_todo_tmp" + + # Extra modules + extra_modules = input( + t("test_install_extra_modules") + ).strip() + + # Log level + log_level = input(t("test_log_level")).strip() + if not log_level: + log_level = "test" + + # Build module list + modules_to_install = module_name + if extra_modules: + modules_to_install += f",{extra_modules}" + + # Step 1: Create temp DB + print(f"\n--- {t('test_creating_db')} '{db_name}' ---") + cmd_restore = ( + f"./script/database/db_restore.py --database {db_name}" + ) + self.execute.exec_command_live( + cmd_restore, + source_erplibre=False, + single_source_erplibre=True, + ) + + # Step 2: Install modules + print(f"\n--- {t('test_installing_modules')}: {modules_to_install} ---") + cmd_install = ( + f"./script/addons/install_addons.sh" + f" {db_name} {modules_to_install}" + ) + self.execute.exec_command_live( + cmd_install, + source_erplibre=False, + single_source_erplibre=True, + ) + + # Step 3: Run tests + print(f"\n--- {t('test_running')}: {module_name} ---") + cmd_test = ( + f"ODOO_MODE_TEST=true" + f" ./run.sh" + f" -d {db_name}" + f" -u {module_name}" + f" --log-level={log_level}" + ) + if coverage: + cmd_test = f"ODOO_MODE_COVERAGE=true {cmd_test}" + status_code, output = self.execute.exec_command_live( + cmd_test, + return_status_and_output=True, + source_erplibre=False, + single_source_erplibre=True, + ) + + if status_code == 0: + print(f"\n✅ {t('test_success')}") + else: + print(f"\n❌ {t('test_failed')} {status_code}") + + # Step 4: Cleanup + lang = get_lang() + keep_input = input(t("test_keep_db")).strip().lower() + keep = keep_input in ( + ("o", "oui") if lang == "fr" else ("y", "yes") + ) + if keep: + print(f"{t('test_db_kept')}: {db_name}") + else: + print(f"\n--- {t('test_cleaning_db')} '{db_name}' ---") + cmd_drop = ( + f"./odoo_bin.sh db --drop --database {db_name}" + ) + self.execute.exec_command_live( + cmd_drop, + source_erplibre=False, + single_source_erplibre=True, + ) + def execute_pip_audit(self): lst_version, lst_version_installed, odoo_installed_version = ( self.get_odoo_version() diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 8a84067..476fa69 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -343,6 +343,75 @@ TRANSLATIONS = { "fr": "Débogage todo.py", "en": "Debug todo.py", }, + # Test section + "menu_test": { + "fr": "Test - Tester un module Odoo", + "en": "Test - Test an Odoo module", + }, + "test_description": { + "fr": "Tester un module Odoo sur une base de données temporaire!", + "en": "Test an Odoo module on a temporary database!", + }, + "test_run_module": { + "fr": "Tester un module", + "en": "Test a module", + }, + "test_run_module_coverage": { + "fr": "Tester un module avec couverture de code", + "en": "Test a module with code coverage", + }, + "test_enter_module_name": { + "fr": "Nom du module à tester : ", + "en": "Module name to test: ", + }, + "test_db_name": { + "fr": "Nom de la base de données temporaire (défaut: test_todo_tmp) : ", + "en": "Temporary database name (default: test_todo_tmp): ", + }, + "test_install_extra_modules": { + "fr": "Modules supplémentaires à installer (séparés par des virgules, vide pour aucun) : ", + "en": "Extra modules to install (comma-separated, empty for none): ", + }, + "test_log_level": { + "fr": "Niveau de log (défaut: test) : ", + "en": "Log level (default: test): ", + }, + "test_creating_db": { + "fr": "Création de la base de données temporaire", + "en": "Creating temporary database", + }, + "test_installing_modules": { + "fr": "Installation des modules", + "en": "Installing modules", + }, + "test_running": { + "fr": "Exécution des tests", + "en": "Running tests", + }, + "test_cleaning_db": { + "fr": "Suppression de la base de données temporaire", + "en": "Cleaning up temporary database", + }, + "test_keep_db": { + "fr": "Conserver la base de données temporaire? (o/N) : ", + "en": "Keep the temporary database? (y/N): ", + }, + "test_db_kept": { + "fr": "Base de données conservée", + "en": "Database kept", + }, + "test_success": { + "fr": "Tests terminés avec succès!", + "en": "Tests completed successfully!", + }, + "test_failed": { + "fr": "Les tests ont échoué avec le code de retour", + "en": "Tests failed with return code", + }, + "test_module_required": { + "fr": "Le nom du module est requis!", + "en": "Module name is required!", + }, # Language selection "lang_prompt": { "fr": "Choisir la langue / Choose language",