From dbb01e0f000a70cb3d48f5969d425118fe5698bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 04:08:22 +0000 Subject: [PATCH 1/8] Initial plan From 4fc9bb4d628f650f05bbb025cdf41202a604cc33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 04:14:48 +0000 Subject: [PATCH 2/8] Add i18n infrastructure with locale.py and language files (es, en) Co-authored-by: Kilynho <40294264+Kilynho@users.noreply.github.com> --- compile.py | 9 ++-- i18n/en.json | 126 ++++++++++++++++++++++++++++++++++++++++++++ i18n/es.json | 126 ++++++++++++++++++++++++++++++++++++++++++++ locale.py | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 85 ++++++++++++++++-------------- report.py | 3 +- 6 files changed, 450 insertions(+), 43 deletions(-) create mode 100644 i18n/en.json create mode 100644 i18n/es.json create mode 100644 locale.py diff --git a/compile.py b/compile.py index c83d55f..1c9d965 100644 --- a/compile.py +++ b/compile.py @@ -17,6 +17,7 @@ import json import time import logger +from locale import get_text as _ class CompilationResult: @@ -62,7 +63,7 @@ def ensure_kernel_configured(kernel_root: Path) -> bool: if config_file.exists(): return True - logger.info("[COMPILE] Kernel not configured. Running 'make defconfig'...") + logger.info(f"[COMPILE] {_('compile.kernel_not_configured')}") try: result = subprocess.run( ['make', 'defconfig'], @@ -73,14 +74,14 @@ def ensure_kernel_configured(kernel_root: Path) -> bool: ) if result.returncode == 0 and config_file.exists(): - logger.info("[COMPILE] ✓ Kernel configured successfully") + logger.info(f"[COMPILE] {_('compile.kernel_configured')}") return True else: - logger.error(f"[COMPILE] ✗ Failed to configure kernel: {result.stderr[:200]}") + logger.error(f"[COMPILE] {_('compile.kernel_config_failed', error=result.stderr[:200])}") return False except Exception as e: - logger.error(f"[COMPILE] ✗ Exception while configuring kernel: {e}") + logger.error(f"[COMPILE] {_('compile.kernel_config_exception', error=e)}") return False diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000..201c793 --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,126 @@ +{ + "analyzer": { + "analyzing": "Analyzing {total} files with {workers} workers...", + "files_to_analyze": "Files to analyze: {files}", + "analyzed_file": "Analyzed {file}: {errors} errors, {warnings} warnings", + "errors_found": "Errors found: {count}", + "warnings_found": "Warnings found: {count}", + "total_found": "Total found: {count}", + "analysis_complete": "✔ Analysis complete.", + "html_generated": "✔ HTML report generated: {path}", + "json_generated": "✔ JSON generated: {path}" + }, + "autofix": { + "processing": "Processing files...", + "json_input": "JSON input: {json_file}, file filter: {filter}", + "modified_file": "Modified file: {file}", + "errors_processed": "Errors processed: {total}", + "warnings_processed": "Warnings processed: {total}", + "fixed": "Fixed: {count} ({percent:.1f}%)", + "skipped": "Skipped: {count} ({percent:.1f}%)", + "total_processed": "Total processed: {total}", + "complete": "✔ Analysis complete {path}", + "html_generated": "✔ HTML report generated: {path}", + "json_generated": "✔ JSON generated: {path}" + }, + "compile": { + "no_modified_files": "No modified files found to compile", + "files_to_compile": "Files to compile: {files}", + "restoring": "Restoring {count} files from backup...", + "kernel_root": "Kernel root: {path}", + "html_generated": "✓ HTML report generated: {path}", + "json_generated": "✓ JSON generated: {path}", + "kernel_not_configured": "Kernel not configured. Running 'make defconfig'...", + "kernel_configured": "✓ Kernel configured successfully", + "kernel_config_failed": "✗ Failed to configure kernel: {error}", + "kernel_config_exception": "✗ Exception while configuring kernel: {error}", + "cleanup_removed": "Removed: {file}", + "cleanup_warning": "Could not clean {file}: {error}" + }, + "errors": { + "files_not_found": "No files found with extensions {extensions}", + "file_not_exist": "File does not exist: {file}", + "kernel_not_found": "Kernel root not found: {path}", + "file_error": "{file}: {error}" + }, + "html": { + "dashboard_title": "Checkpatch Dashboard", + "analyzer_title": "Checkpatch Analyzer Report", + "autofix_title": "Checkpatch Autofix Report", + "compile_title": "Compilation Report", + "detail_reason_title": "Detail by Reason", + "detail_file_title": "Detail by File", + "global_summary": "Global Summary", + "detail_by_reason": "Detail by Reason", + "detail_by_file": "Detail by File", + "compilation_results": "Compilation Results", + "status": "Status", + "files": "Files", + "files_percent": "% Files", + "cases": "Cases", + "cases_percent": "% Cases", + "errors_fixed": "ERRORS FIXED", + "errors_skipped": "ERRORS SKIPPED", + "warnings_fixed": "WARNINGS FIXED", + "warnings_skipped": "WARNINGS SKIPPED", + "total": "TOTAL", + "errors": "ERRORS", + "warnings": "WARNINGS", + "error": "Error", + "warning": "Warning", + "type": "Type", + "count": "Count", + "percent": "Percentage", + "file": "File", + "line": "Line", + "message": "Message", + "fixed": "Fixed", + "skipped": "Skipped", + "yes": "Yes", + "no": "No", + "compilation_success": "✓ Compilation successful", + "compilation_failed": "✗ Compilation failed", + "successful": "Successful", + "failed": "Failed", + "navigation": "Navigation", + "back_to_dashboard": "← Back to Dashboard", + "view_details": "View details", + "expand_all": "Expand all", + "collapse_all": "Collapse all", + "no_changes": "No changes", + "diff": "Diff" + }, + "cli": { + "mode_group": "Operation modes", + "analyze_help": "Analysis mode: run checkpatch and generate HTML report", + "fix_help": "Autofix mode: apply automatic fixes to warnings/errors", + "compile_help": "Compilation mode: test compile modified files", + "analyze_options": "Analysis options", + "autofix_options": "Autofix options", + "compile_options": "Compilation options", + "logging_options": "Logging options", + "source_dir_help": "Directories with files to analyze (can be specified multiple times)", + "paths_help": "Alias for --source-dir (for compatibility)", + "extensions_help": "File extensions to analyze", + "checkpatch_help": "Path to checkpatch.pl script", + "kernel_root_help": "Kernel root path (for checkpatch)", + "workers_help": "Number of parallel workers", + "json_input_help": "Input JSON with issues", + "json_output_help": "Output path for JSON (autofix)", + "file_filter_help": "Filter only this file (autofix)", + "restore_after_help": "Restore files from backup after compilation", + "no_cleanup_help": "Don't clean .o files after compilation", + "log_level_help": "Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)", + "log_file_help": "File to save logs", + "no_color_help": "Disable colors in output", + "language_help": "Interface language (es, en)", + "error_no_mode": "Error: Must specify at least one mode (--analyze, --fix, --compile)", + "error_analyze_missing": "Error: --analyze requires --source-dir (or --paths)", + "error_fix_missing": "Error: --fix requires --json-input", + "error_compile_missing": "Error: --compile requires --json-input and --kernel-root" + }, + "main": { + "arguments": "Arguments: {args}", + "log_level": "Logging level: {level}" + } +} diff --git a/i18n/es.json b/i18n/es.json new file mode 100644 index 0000000..9da6dec --- /dev/null +++ b/i18n/es.json @@ -0,0 +1,126 @@ +{ + "analyzer": { + "analyzing": "Analizando {total} archivos con {workers} workers...", + "files_to_analyze": "Archivos a analizar: {files}", + "analyzed_file": "Analizado {file}: {errors} errores, {warnings} warnings", + "errors_found": "Errores encontrados: {count}", + "warnings_found": "Warnings encontrados: {count}", + "total_found": "Total encontrados: {count}", + "analysis_complete": "✔ Análisis terminado.", + "html_generated": "✔ Informe HTML generado: {path}", + "json_generated": "✔ JSON generado: {path}" + }, + "autofix": { + "processing": "Procesando archivos...", + "json_input": "JSON de entrada: {json_file}, filtro de archivo: {filter}", + "modified_file": "Modificado archivo: {file}", + "errors_processed": "Errores procesados: {total}", + "warnings_processed": "Warnings procesados: {total}", + "fixed": "Corregidos: {count} ({percent:.1f}%)", + "skipped": "Saltados : {count} ({percent:.1f}%)", + "total_processed": "Total procesados: {total}", + "complete": "✔ Análisis terminado {path}", + "html_generated": "✔ Informe HTML generado : {path}", + "json_generated": "✔ JSON generado: {path}" + }, + "compile": { + "no_modified_files": "No se encontraron archivos modificados para compilar", + "files_to_compile": "Archivos a compilar: {files}", + "restoring": "Restaurando {count} archivos desde backup...", + "kernel_root": "Kernel root: {path}", + "html_generated": "✓ Informe HTML generado: {path}", + "json_generated": "✓ JSON generado: {path}", + "kernel_not_configured": "Kernel not configured. Running 'make defconfig'...", + "kernel_configured": "✓ Kernel configured successfully", + "kernel_config_failed": "✗ Failed to configure kernel: {error}", + "kernel_config_exception": "✗ Exception while configuring kernel: {error}", + "cleanup_removed": "Removed: {file}", + "cleanup_warning": "Could not clean {file}: {error}" + }, + "errors": { + "files_not_found": "No se encontraron archivos con extensiones {extensions}", + "file_not_exist": "No existe el archivo: {file}", + "kernel_not_found": "Kernel root no encontrado: {path}", + "file_error": "{file}: {error}" + }, + "html": { + "dashboard_title": "Checkpatch Dashboard", + "analyzer_title": "Informe Checkpatch Analyzer", + "autofix_title": "Informe Checkpatch Autofix", + "compile_title": "Informe de Compilación", + "detail_reason_title": "Detalle por motivo", + "detail_file_title": "Detalle por fichero", + "global_summary": "Resumen global", + "detail_by_reason": "Detalle por motivo", + "detail_by_file": "Detalle por fichero", + "compilation_results": "Compilation Results", + "status": "Estado", + "files": "Ficheros", + "files_percent": "% Ficheros", + "cases": "Casos", + "cases_percent": "% Casos", + "errors_fixed": "ERRORES CORREGIDOS", + "errors_skipped": "ERRORES SALTADOS", + "warnings_fixed": "WARNINGS CORREGIDOS", + "warnings_skipped": "WARNINGS SALTADOS", + "total": "TOTAL", + "errors": "ERRORES", + "warnings": "WARNINGS", + "error": "Error", + "warning": "Warning", + "type": "Tipo", + "count": "Cantidad", + "percent": "Porcentaje", + "file": "Fichero", + "line": "Línea", + "message": "Mensaje", + "fixed": "Corregido", + "skipped": "Saltado", + "yes": "Sí", + "no": "No", + "compilation_success": "✓ Compilación exitosa", + "compilation_failed": "✗ Compilación fallida", + "successful": "Exitosas", + "failed": "Fallidas", + "navigation": "Navegación", + "back_to_dashboard": "← Volver al Dashboard", + "view_details": "Ver detalles", + "expand_all": "Expandir todo", + "collapse_all": "Colapsar todo", + "no_changes": "No changes", + "diff": "Diff" + }, + "cli": { + "mode_group": "Modos de operación", + "analyze_help": "Modo análisis: ejecuta checkpatch y genera reporte HTML", + "fix_help": "Modo autofix: aplica correcciones automáticas a warnings/errores", + "compile_help": "Modo compilación: prueba compilar archivos modificados", + "analyze_options": "Opciones de análisis", + "autofix_options": "Opciones de autofix", + "compile_options": "Opciones de compilación", + "logging_options": "Opciones de logging", + "source_dir_help": "Directorios con archivos a analizar (puede especificarse múltiples veces)", + "paths_help": "Alias para --source-dir (para compatibilidad)", + "extensions_help": "Extensiones de archivo a analizar", + "checkpatch_help": "Ruta al script checkpatch.pl", + "kernel_root_help": "Ruta raíz del kernel (para checkpatch)", + "workers_help": "Número de workers paralelos", + "json_input_help": "JSON de entrada con issues", + "json_output_help": "Ruta de salida para JSON (autofix)", + "file_filter_help": "Filtrar solo este archivo (autofix)", + "restore_after_help": "Restaurar archivos desde backup después de compilar", + "no_cleanup_help": "No limpiar archivos .o después de compilar", + "log_level_help": "Nivel de logging (DEBUG, INFO, WARNING, ERROR, CRITICAL)", + "log_file_help": "Archivo donde guardar los logs", + "no_color_help": "Desactivar colores en la salida", + "language_help": "Idioma de la interfaz (es, en)", + "error_no_mode": "Error: Debe especificar al menos un modo (--analyze, --fix, --compile)", + "error_analyze_missing": "Error: --analyze requiere --source-dir (o --paths)", + "error_fix_missing": "Error: --fix requiere --json-input", + "error_compile_missing": "Error: --compile requiere --json-input y --kernel-root" + }, + "main": { + "arguments": "Argumentos: {args}", + "log_level": "Nivel de logging: {level}" + } +} diff --git a/locale.py b/locale.py new file mode 100644 index 0000000..a3d70c4 --- /dev/null +++ b/locale.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +locale.py - Sistema de internacionalización (i18n) para checkpatch + +Proporciona soporte multiidioma mediante archivos JSON de localización. +""" + +import json +import os +from pathlib import Path + + +class LocaleManager: + """Gestor de localización singleton para toda la aplicación.""" + + _instance = None + _initialized = False + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + if not self._initialized: + self.current_language = "es" # Idioma por defecto: español + self.strings = {} + self.i18n_dir = Path(__file__).parent / "i18n" + self._initialized = True + + def set_language(self, language_code): + """ + Establece el idioma activo y carga las cadenas correspondientes. + + Args: + language_code: Código de idioma (ej: 'es', 'en') + """ + self.current_language = language_code + self._load_strings() + + def _load_strings(self): + """Carga las cadenas del archivo de idioma actual.""" + lang_file = self.i18n_dir / f"{self.current_language}.json" + + if not lang_file.exists(): + # Fallback a español si el idioma no existe + print(f"[WARNING] Language file not found: {lang_file}") + if self.current_language != "es": + self.current_language = "es" + lang_file = self.i18n_dir / "es.json" + + try: + with open(lang_file, 'r', encoding='utf-8') as f: + self.strings = json.load(f) + except Exception as e: + print(f"[ERROR] Failed to load language file {lang_file}: {e}") + self.strings = {} + + def get(self, key, **kwargs): + """ + Obtiene una cadena localizada por su clave. + + Args: + key: Clave de la cadena (puede usar notación punto: 'section.key') + **kwargs: Parámetros para formatear la cadena + + Returns: + Cadena localizada y formateada + """ + # Navegar por la estructura JSON usando notación punto + parts = key.split('.') + value = self.strings + + for part in parts: + if isinstance(value, dict) and part in value: + value = value[part] + else: + # Si no se encuentra la clave, retornar la clave misma como fallback + return f"[MISSING: {key}]" + + # Si es un string, formatear con los parámetros + if isinstance(value, str): + try: + return value.format(**kwargs) + except KeyError as e: + return f"[FORMAT_ERROR: {key} - missing param {e}]" + + return value + + def get_language(self): + """Retorna el código del idioma actual.""" + return self.current_language + + def get_available_languages(self): + """Retorna lista de idiomas disponibles.""" + if not self.i18n_dir.exists(): + return [] + + langs = [] + for file in self.i18n_dir.glob("*.json"): + langs.append(file.stem) + return sorted(langs) + + +# Instancia global singleton +_locale_manager = LocaleManager() + + +def set_language(language_code): + """ + Establece el idioma de la aplicación. + + Args: + language_code: Código de idioma (ej: 'es', 'en') + """ + _locale_manager.set_language(language_code) + + +def get_text(key, **kwargs): + """ + Obtiene texto localizado. + + Args: + key: Clave del texto (notación punto: 'section.key') + **kwargs: Parámetros para formatear el texto + + Returns: + Texto localizado + """ + return _locale_manager.get(key, **kwargs) + + +def get_language(): + """Retorna el idioma actual.""" + return _locale_manager.get_language() + + +def get_available_languages(): + """Retorna lista de idiomas disponibles.""" + return _locale_manager.get_available_languages() + + +# Alias corto para conveniencia +_ = get_text diff --git a/main.py b/main.py index f6beb72..9025f27 100755 --- a/main.py +++ b/main.py @@ -20,6 +20,10 @@ # Sistema de logging import logger +# Sistema de internacionalización +import locale as i18n_module +from locale import get_text as _ + # Módulos unificados from engine import ( apply_fixes, @@ -58,7 +62,7 @@ def analyze_mode(args): all_files.extend(files) if not all_files: - logger.error(f"[ERROR] No se encontraron archivos con extensiones {args.extensions}") + logger.error(f"[ERROR] {_('errors.files_not_found', extensions=args.extensions)}") return 1 checkpatch_script = args.checkpatch @@ -82,8 +86,8 @@ def progress_bar(current, total): bar = '#' * filled + ' ' * (bar_len - filled) return f"[{bar}] {percent:.1f}% ({current}/{total})" - logger.info(f"[ANALYZER] Analizando {total} archivos con {args.workers} workers...") - logger.debug(f"[ANALYZER] Archivos a analizar: {[str(f) for f in all_files[:5]]}{'...' if len(all_files) > 5 else ''}") + logger.info(f"[ANALYZER] {_('analyzer.analyzing', total=total, workers=args.workers)}") + logger.debug(f"[ANALYZER] {_('analyzer.files_to_analyze', files=[str(f) for f in all_files[:5]])}" + "{'...' if len(all_files) > 5 else ''}") with ThreadPoolExecutor(max_workers=args.workers) as executor: futures = {executor.submit(analyze_file, f, checkpatch_script, kernel_root): f for f in all_files} @@ -106,10 +110,10 @@ def progress_bar(current, total): completed += 1 if completed % 10 == 0 or completed == total: print(f"\r[ANALYZER] Progreso: {progress_bar(completed, total)}", end="") - logger.debug(f"[ANALYZER] Analizado {file_path}: {len(errors)} errores, {len(warnings)} warnings") + logger.debug(f"[ANALYZER] {_('analyzer.analyzed_file', file=file_path, errors=len(errors), warnings=len(warnings))}") except Exception as e: - logger.error(f"\n[ERROR] {file_path}: {e}") + logger.error(f"\n[ERROR] {_('errors.file_error', file=file_path, error=e)}") print() # Nueva línea después de la barra @@ -142,12 +146,12 @@ def progress_bar(current, total): error_count = sum(analysis_data["error_reasons"].values()) warning_count = sum(analysis_data["warning_reasons"].values()) - logger.info(f"[ANALYZER] Errores encontrados: {error_count}") - logger.info(f"[ANALYZER] Warnings encontrados: {warning_count}") - logger.info(f"[ANALYZER] Total encontrados: {error_count + warning_count}") - logger.info(f"[ANALYZER] ✔ Análisis terminado.") - logger.info(f"[ANALYZER] ✔ Informe HTML generado: {html_path}") - logger.info(f"[ANALYZER] ✔ JSON generado: {json_path}") + logger.info(f"[ANALYZER] {_('analyzer.errors_found', count=error_count)}") + logger.info(f"[ANALYZER] {_('analyzer.warnings_found', count=warning_count)}") + logger.info(f"[ANALYZER] {_('analyzer.total_found', count=error_count + warning_count)}") + logger.info(f"[ANALYZER] {_('analyzer.analysis_complete')}") + logger.info(f"[ANALYZER] {_('analyzer.html_generated', path=html_path)}") + logger.info(f"[ANALYZER] {_('analyzer.json_generated', path=json_path)}") return 0 @@ -157,7 +161,7 @@ def fix_mode(args): json_file = Path(args.json_input) if not json_file.exists(): - logger.error(f"[ERROR] No existe el archivo: {json_file}") + logger.error(f"[ERROR] {_('errors.file_not_exist', file=json_file)}") return 1 with open(json_file, "r") as f: @@ -170,8 +174,8 @@ def fix_mode(args): file_filter = Path(args.file).resolve() if args.file else None - logger.info("[AUTOFIX] Procesando archivos...") - logger.debug(f"[AUTOFIX] JSON de entrada: {json_file}, filtro de archivo: {file_filter}") + logger.info(f"[AUTOFIX] {_('autofix.processing')}") + logger.debug(f"[AUTOFIX] {_('autofix.json_input', json_file=json_file, filter=file_filter)}") for entry in files_data: file_path = Path(entry["file"]).resolve() @@ -215,7 +219,7 @@ def fix_mode(args): if file_modified: modified_files.add(str(file_path)) logger.info(f"[AUTOFIX] - {file_path.relative_to(file_path.parent.parent.parent)}") - logger.debug(f"[AUTOFIX] Modificado archivo: {file_path}") + logger.debug(f"[AUTOFIX] {_('autofix.modified_file', file=file_path)}") # Calcular estadísticas para resumen errors_fixed = sum(1 for issues in report_data.values() for i in issues.get("error", []) if i.get("fixed")) @@ -225,21 +229,21 @@ def fix_mode(args): # Resumen en consola if errors_fixed + errors_skipped > 0: - logger.info(f"[AUTOFIX] Errores procesados: {errors_fixed + errors_skipped}") - logger.info(f"[AUTOFIX] - Corregidos: {errors_fixed} ({100*errors_fixed/(errors_fixed+errors_skipped):.1f}%)") - logger.info(f"[AUTOFIX] - Saltados : {errors_skipped} ({100*errors_skipped/(errors_fixed+errors_skipped):.1f}%)") + logger.info(f"[AUTOFIX] {_('autofix.errors_processed', total=errors_fixed + errors_skipped)}") + logger.info(f"[AUTOFIX] - {_('autofix.fixed', count=errors_fixed, percent=100*errors_fixed/(errors_fixed+errors_skipped))}") + logger.info(f"[AUTOFIX] - {_('autofix.skipped', count=errors_skipped, percent=100*errors_skipped/(errors_fixed+errors_skipped))}") if warnings_fixed + warnings_skipped > 0: - logger.info(f"[AUTOFIX] Warnings procesados: {warnings_fixed + warnings_skipped}") - logger.info(f"[AUTOFIX] - Corregidos: {warnings_fixed} ({100*warnings_fixed/(warnings_fixed+warnings_skipped):.1f}%)") - logger.info(f"[AUTOFIX] - Saltados : {warnings_skipped} ({100*warnings_skipped/(warnings_fixed+warnings_skipped):.1f}%)") + logger.info(f"[AUTOFIX] {_('autofix.warnings_processed', total=warnings_fixed + warnings_skipped)}") + logger.info(f"[AUTOFIX] - {_('autofix.fixed', count=warnings_fixed, percent=100*warnings_fixed/(warnings_fixed+warnings_skipped))}") + logger.info(f"[AUTOFIX] - {_('autofix.skipped', count=warnings_skipped, percent=100*warnings_skipped/(warnings_fixed+warnings_skipped))}") total = errors_fixed + warnings_fixed + errors_skipped + warnings_skipped total_fixed = errors_fixed + warnings_fixed if total > 0: - logger.info(f"[AUTOFIX] Total procesados: {total}") - logger.info(f"[AUTOFIX] - Corregidos: {total_fixed} ({100*total_fixed/total:.1f}%)") - logger.info(f"[AUTOFIX] - Saltados : {total - total_fixed} ({100*(total-total_fixed)/total:.1f}%)") + logger.info(f"[AUTOFIX] {_('autofix.total_processed', total=total)}") + logger.info(f"[AUTOFIX] - {_('autofix.fixed', count=total_fixed, percent=100*total_fixed/total)}") + logger.info(f"[AUTOFIX] - {_('autofix.skipped', count=total - total_fixed, percent=100*(total-total_fixed)/total)}") # Generar HTML html_path = Path(args.html) @@ -260,9 +264,9 @@ def fix_mode(args): with open(json_out_path, "w", encoding="utf-8") as f: json.dump(report_data, f, indent=2, default=str) - logger.info(f"[AUTOFIX] ✔ Análisis terminado {json_out_path}") - logger.info(f"[AUTOFIX] ✔ Informe HTML generado : {html_path}") - logger.info(f"[AUTOFIX] ✔ JSON generado: {json_out_path}") + logger.info(f"[AUTOFIX] {_('autofix.complete', path=json_out_path)}") + logger.info(f"[AUTOFIX] {_('autofix.html_generated', path=html_path)}") + logger.info(f"[AUTOFIX] {_('autofix.json_generated', path=json_out_path)}") return 0 @@ -272,7 +276,7 @@ def compile_mode(args): json_file = Path(args.json_input) if not json_file.exists(): - logger.error(f"[ERROR] No existe el archivo: {json_file}") + logger.error(f"[ERROR] {_('errors.file_not_exist', file=json_file)}") return 1 # Leer archivos modificados del JSON de autofix @@ -299,23 +303,23 @@ def compile_mode(args): modified_files = [Path(entry["file"]) for entry in report_data] if not modified_files: - logger.info("[COMPILE] No se encontraron archivos modificados para compilar") + logger.info(f"[COMPILE] {_('compile.no_modified_files')}") return 0 - logger.debug(f"[COMPILE] Archivos a compilar: {[str(f) for f in modified_files]}") + logger.debug(f"[COMPILE] {_('compile.files_to_compile', files=[str(f) for f in modified_files])}") # Restaurar backups si se solicita if args.restore_before: - logger.info(f"[COMPILE] Restaurando {len(modified_files)} archivos desde backup...") + logger.info(f"[COMPILE] {_('compile.restoring', count=len(modified_files))}") restore_backups(modified_files) # Compilar archivos kernel_root = Path(args.kernel_root).resolve() if not kernel_root.exists(): - logger.error(f"[ERROR] Kernel root no encontrado: {kernel_root}") + logger.error(f"[ERROR] {_('errors.kernel_not_found', path=kernel_root)}") return 1 - logger.info(f"[COMPILE] Kernel root: {kernel_root}") + logger.info(f"[COMPILE] {_('compile.kernel_root', path=kernel_root)}") results = compile_modified_files( modified_files, kernel_root, @@ -324,7 +328,7 @@ def compile_mode(args): # Restaurar backups después si se solicita if args.restore_after: - logger.info(f"\n[COMPILE] Restaurando {len(modified_files)} archivos desde backup...") + logger.info(f"\n[COMPILE] {_('compile.restoring', count=len(modified_files))}") restore_backups(modified_files) # Generar reportes @@ -339,8 +343,8 @@ def compile_mode(args): # Resumen en consola print_summary(results) - logger.info(f"\n[COMPILE] ✓ Informe HTML generado: {html_path}") - logger.info(f"[COMPILE] ✓ JSON generado: {json_path}") + logger.info(f"\n[COMPILE] {_('compile.html_generated', path=html_path)}") + logger.info(f"[COMPILE] {_('compile.json_generated', path=json_path)}") # Retornar 0 si todos compilaron exitosamente, 1 si hubo fallos failed_count = sum(1 for r in results if not r.success) @@ -411,9 +415,14 @@ def main(): help="Archivo de log opcional (ej: logs/checkpatch.log)") logging_group.add_argument("--no-color", action="store_true", help="Desactivar colores en la salida de consola") + logging_group.add_argument("--language", choices=["es", "en"], default="es", + help="Idioma de la interfaz (default: es)") args = parser.parse_args() + # Configurar idioma + i18n_module.set_language(args.language) + # Configurar logging log_level = logger.get_level_from_string(args.log_level) logger.setup_logging( @@ -422,8 +431,8 @@ def main(): use_colors=not args.no_color ) - logger.debug(f"[MAIN] Argumentos: {vars(args)}") - logger.debug(f"[MAIN] Nivel de logging: {args.log_level}") + logger.debug(f"[MAIN] {_('main.arguments', args=vars(args))}") + logger.debug(f"[MAIN] {_('main.log_level', level=args.log_level)}") # Validar argumentos según modo if args.analyze: diff --git a/report.py b/report.py index b837bdc..0f3ad72 100644 --- a/report.py +++ b/report.py @@ -10,6 +10,7 @@ import subprocess import hashlib from utils import COMMON_CSS, percentage, bar_width, percentage_value +from locale import get_text as _ # ============================ # HELPER FUNCTIONS - COMMON @@ -312,7 +313,7 @@ def generate_html_report(report_data, html_file, kernel_dir="."): o_pct = percentage(o_count, o_count_errors_total) f_bar = bar_width(f_count, f_count_errors_total, max_width=PCT_CELL_WIDTH - 50) o_bar = bar_width(o_count, o_count_errors_total, max_width=PCT_CELL_WIDTH - 50) - append(f"
| Estado | Ficheros | " - f"% Ficheros | " - f"Casos | " - f"% Casos |
|---|---|---|---|---|
| {_('html.status')} | {_('html.files')} | " + f"{_('html.files_percent')} | " + f"{_('html.cases')} | " + f"{_('html.cases_percent')} |