Merge branch 'develop'
- support i18n script TODO - support command test script TODO - support commande security check python venv TODO - update claude agent - change default version odoo 18
This commit is contained in:
commit
300f43814d
8 changed files with 966 additions and 116 deletions
81
CLAUDE.md
81
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
|
||||
|
||||
|
|
@ -246,3 +269,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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
doc/AGENT.INFO.md
Normal file
5
doc/AGENT.INFO.md
Normal file
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ sshconf
|
|||
cloudflare
|
||||
psutil
|
||||
mmg
|
||||
pip-audit
|
||||
|
||||
# For mobile
|
||||
pillow
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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,17 +229,20 @@ 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
|
||||
[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_test")}
|
||||
[12] {t("menu_lang")}
|
||||
[0] {t("back")}
|
||||
"""
|
||||
while True:
|
||||
status = click.prompt(help_info)
|
||||
|
|
@ -252,8 +285,20 @@ 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
|
||||
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
|
||||
else:
|
||||
print("Commande non trouvée 🤖!")
|
||||
print(t("cmd_not_found"))
|
||||
|
||||
def prompt_install(self):
|
||||
print("Detect first installation from code source.")
|
||||
|
|
@ -333,7 +378,7 @@ class TODO:
|
|||
),
|
||||
"0": (
|
||||
"0",
|
||||
"0: Quitter",
|
||||
f"0: {t('menu_quit')}",
|
||||
),
|
||||
}
|
||||
dct_final_cmd_intern = {}
|
||||
|
|
@ -369,11 +414,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()
|
||||
|
|
@ -383,7 +428,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
|
||||
|
|
@ -393,9 +438,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))
|
||||
|
|
@ -448,12 +491,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
|
||||
|
||||
|
|
@ -467,14 +513,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)
|
||||
|
|
@ -491,9 +537,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,
|
||||
|
|
@ -510,7 +554,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")
|
||||
|
|
@ -532,11 +576,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
|
||||
|
|
@ -547,11 +591,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)
|
||||
|
|
@ -577,10 +621,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
|
||||
|
|
@ -596,18 +640,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"),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -635,15 +679,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)
|
||||
|
||||
|
|
@ -684,16 +728,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)
|
||||
|
||||
|
|
@ -709,12 +751,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)
|
||||
|
||||
|
|
@ -726,16 +768,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)
|
||||
|
||||
|
|
@ -755,13 +797,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)
|
||||
|
||||
|
|
@ -775,7 +821,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
|
||||
|
|
@ -801,6 +847,204 @@ class TODO:
|
|||
single_source_erplibre=True,
|
||||
)
|
||||
|
||||
def prompt_execute_security(self):
|
||||
print(f"🤖 {t('security_audit')}")
|
||||
lst_choice = [
|
||||
{"prompt_description": t("pip_audit_desc")},
|
||||
]
|
||||
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(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()
|
||||
)
|
||||
|
||||
# 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 += f" - {t('current')}"
|
||||
if dct_version.get("default"):
|
||||
label += f" - {t('default')}"
|
||||
|
||||
dct_env[key_s] = {
|
||||
"label": label,
|
||||
"venv_path": venv_path,
|
||||
"req_path": req_path,
|
||||
"erplibre_version": erplibre_version,
|
||||
}
|
||||
|
||||
if not dct_env:
|
||||
print(t("no_env_installed"))
|
||||
return
|
||||
|
||||
# Show selection menu
|
||||
str_input = (
|
||||
f"💬 {t('choose_env_audit')}\n\t"
|
||||
+ "\n\t".join([v["label"] for v in dct_env.values()])
|
||||
+ 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"{t('error_value')}" 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"{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"{t('execution')}{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 = (
|
||||
|
|
@ -821,10 +1065,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)
|
||||
|
|
@ -853,11 +1097,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)
|
||||
|
||||
|
|
@ -872,7 +1116,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()
|
||||
|
|
@ -917,7 +1161,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:
|
||||
|
|
@ -1076,14 +1320,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"
|
||||
|
|
@ -1312,7 +1557,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(
|
||||
|
|
@ -1480,13 +1725,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")
|
||||
|
|
|
|||
520
script/todo/todo_i18n.py
Normal file
520
script/todo/todo_i18n.py
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
#!/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",
|
||||
},
|
||||
# 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",
|
||||
"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))
|
||||
Loading…
Reference in a new issue