v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution

- Frontend v4 accessible sur réseau local (192.168.1.40)
- Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard)
- Ollama GPU fonctionnel
- Self-healing interactif
- Dashboard confiance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-29 11:23:51 +01:00
parent 21bfa3b337
commit a27b74cf22
1595 changed files with 412691 additions and 400 deletions

View File

@@ -0,0 +1,269 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Validation des Traductions - RPA Vision V3
Auteur : Dom, Alice, Kiro - 7 janvier 2026
Script pour valider la cohérence et la complétude des fichiers de traduction.
"""
import json
import os
from pathlib import Path
from typing import Dict, List, Set, Any
class TranslationValidator:
"""
Validateur pour les fichiers de traduction
"""
def __init__(self, i18n_dir: str = "i18n"):
self.i18n_dir = Path(i18n_dir)
self.config = self.load_config()
self.translations = {}
self.errors = []
self.warnings = []
def load_config(self) -> Dict[str, Any]:
"""Charge la configuration i18n"""
config_path = self.i18n_dir / "config.json"
try:
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
self.errors.append(f"Erreur lors du chargement de config.json: {e}")
return {}
def load_translations(self) -> None:
"""Charge tous les fichiers de traduction"""
if not self.config:
return
for lang in self.config.get('supportedLanguages', []):
lang_code = lang['code']
lang_file = self.i18n_dir / f"{lang_code}.json"
try:
with open(lang_file, 'r', encoding='utf-8') as f:
self.translations[lang_code] = json.load(f)
print(f"✅ Chargé: {lang_code}.json")
except FileNotFoundError:
self.errors.append(f"Fichier manquant: {lang_code}.json")
except json.JSONDecodeError as e:
self.errors.append(f"JSON invalide dans {lang_code}.json: {e}")
except Exception as e:
self.errors.append(f"Erreur lors du chargement de {lang_code}.json: {e}")
def get_all_keys(self, obj: Dict[str, Any], prefix: str = "") -> Set[str]:
"""Extrait toutes les clés d'un objet JSON imbriqué"""
keys = set()
for key, value in obj.items():
full_key = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
keys.update(self.get_all_keys(value, full_key))
else:
keys.add(full_key)
return keys
def validate_structure(self) -> None:
"""Valide la structure des fichiers de traduction"""
if not self.translations:
self.errors.append("Aucun fichier de traduction chargé")
return
# Obtenir les clés de référence (langue par défaut)
default_lang = self.config.get('defaultLanguage', 'fr')
if default_lang not in self.translations:
self.errors.append(f"Langue par défaut '{default_lang}' non trouvée")
return
reference_keys = self.get_all_keys(self.translations[default_lang])
print(f"📋 Clés de référence ({default_lang}): {len(reference_keys)}")
# Vérifier chaque langue
for lang_code, translations in self.translations.items():
if lang_code == default_lang:
continue
lang_keys = self.get_all_keys(translations)
# Clés manquantes
missing_keys = reference_keys - lang_keys
if missing_keys:
self.errors.append(f"Clés manquantes dans {lang_code}: {sorted(missing_keys)}")
# Clés supplémentaires
extra_keys = lang_keys - reference_keys
if extra_keys:
self.warnings.append(f"Clés supplémentaires dans {lang_code}: {sorted(extra_keys)}")
print(f"🔍 {lang_code}: {len(lang_keys)} clés ({len(missing_keys)} manquantes, {len(extra_keys)} supplémentaires)")
def validate_placeholders(self) -> None:
"""Valide les placeholders dans les traductions"""
import re
placeholder_pattern = re.compile(r'\{\{(\w+)\}\}')
for lang_code, translations in self.translations.items():
self._validate_placeholders_recursive(translations, lang_code, placeholder_pattern)
def _validate_placeholders_recursive(self, obj: Dict[str, Any], lang_code: str, pattern, prefix: str = "") -> None:
"""Valide récursivement les placeholders"""
for key, value in obj.items():
full_key = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
self._validate_placeholders_recursive(value, lang_code, pattern, full_key)
elif isinstance(value, str):
placeholders = pattern.findall(value)
if placeholders:
# Vérifier la cohérence avec la langue de référence
default_lang = self.config.get('defaultLanguage', 'fr')
if lang_code != default_lang and default_lang in self.translations:
ref_value = self._get_nested_value(self.translations[default_lang], full_key.split('.'))
if ref_value:
ref_placeholders = pattern.findall(ref_value)
if set(placeholders) != set(ref_placeholders):
self.warnings.append(
f"Placeholders différents dans {lang_code}.{full_key}: "
f"{placeholders} vs {ref_placeholders}"
)
def _get_nested_value(self, obj: Dict[str, Any], keys: List[str]) -> str:
"""Obtient une valeur imbriquée"""
current = obj
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return ""
return current if isinstance(current, str) else ""
def validate_empty_values(self) -> None:
"""Valide qu'il n'y a pas de valeurs vides"""
for lang_code, translations in self.translations.items():
self._validate_empty_recursive(translations, lang_code)
def _validate_empty_recursive(self, obj: Dict[str, Any], lang_code: str, prefix: str = "") -> None:
"""Valide récursivement les valeurs vides"""
for key, value in obj.items():
full_key = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
self._validate_empty_recursive(value, lang_code, full_key)
elif isinstance(value, str):
if not value.strip():
self.warnings.append(f"Valeur vide dans {lang_code}.{full_key}")
def validate_config(self) -> None:
"""Valide la configuration"""
required_fields = ['defaultLanguage', 'supportedLanguages', 'fallbackLanguage']
for field in required_fields:
if field not in self.config:
self.errors.append(f"Champ manquant dans config.json: {field}")
# Vérifier les langues supportées
if 'supportedLanguages' in self.config:
for i, lang in enumerate(self.config['supportedLanguages']):
required_lang_fields = ['code', 'name', 'flag', 'currency']
for field in required_lang_fields:
if field not in lang:
self.errors.append(f"Champ manquant dans supportedLanguages[{i}]: {field}")
# Vérifier que la langue par défaut est supportée
default_lang = self.config.get('defaultLanguage')
supported_codes = [lang['code'] for lang in self.config.get('supportedLanguages', [])]
if default_lang and default_lang not in supported_codes:
self.errors.append(f"Langue par défaut '{default_lang}' non dans supportedLanguages")
def generate_report(self) -> str:
"""Génère un rapport de validation"""
report = []
report.append("=" * 60)
report.append("RAPPORT DE VALIDATION DES TRADUCTIONS")
report.append("=" * 60)
report.append("")
# Statistiques
report.append("📊 STATISTIQUES:")
report.append(f" • Langues configurées: {len(self.config.get('supportedLanguages', []))}")
report.append(f" • Fichiers chargés: {len(self.translations)}")
report.append(f" • Erreurs trouvées: {len(self.errors)}")
report.append(f" • Avertissements: {len(self.warnings)}")
report.append("")
# Erreurs
if self.errors:
report.append("❌ ERREURS:")
for error in self.errors:
report.append(f"{error}")
report.append("")
# Avertissements
if self.warnings:
report.append("⚠️ AVERTISSEMENTS:")
for warning in self.warnings:
report.append(f"{warning}")
report.append("")
# Résumé
if not self.errors and not self.warnings:
report.append("✅ VALIDATION RÉUSSIE: Aucun problème détecté!")
elif not self.errors:
report.append("✅ VALIDATION RÉUSSIE: Quelques avertissements mineurs")
else:
report.append("❌ VALIDATION ÉCHOUÉE: Des erreurs doivent être corrigées")
report.append("")
report.append("=" * 60)
return "\n".join(report)
def run_validation(self) -> bool:
"""Exécute toutes les validations"""
print("🔍 Démarrage de la validation des traductions...")
print()
# Validation de la configuration
print("📋 Validation de la configuration...")
self.validate_config()
# Chargement des traductions
print("📂 Chargement des fichiers de traduction...")
self.load_translations()
if not self.translations:
print("❌ Aucun fichier de traduction chargé")
return False
# Validations
print("🔍 Validation de la structure...")
self.validate_structure()
print("🔍 Validation des placeholders...")
self.validate_placeholders()
print("🔍 Validation des valeurs vides...")
self.validate_empty_values()
# Génération du rapport
print()
print(self.generate_report())
return len(self.errors) == 0
def main():
"""Point d'entrée principal"""
validator = TranslationValidator()
success = validator.run_validation()
return 0 if success else 1
if __name__ == "__main__":
exit(main())