diff --git a/__pycache__/menu_pasos_traduccion.cpython-310.pyc b/__pycache__/menu_pasos_traduccion.cpython-310.pyc index a4243fc..6353381 100644 Binary files a/__pycache__/menu_pasos_traduccion.cpython-310.pyc and b/__pycache__/menu_pasos_traduccion.cpython-310.pyc differ diff --git a/__pycache__/x2_master_export2translate.cpython-310.pyc b/__pycache__/x2_master_export2translate.cpython-310.pyc index 3617308..e888f8c 100644 Binary files a/__pycache__/x2_master_export2translate.cpython-310.pyc and b/__pycache__/x2_master_export2translate.cpython-310.pyc differ diff --git a/__pycache__/x3_llm_auto_translate.cpython-310.pyc b/__pycache__/x3_llm_auto_translate.cpython-310.pyc index 9935c54..b0b9aa6 100644 Binary files a/__pycache__/x3_llm_auto_translate.cpython-310.pyc and b/__pycache__/x3_llm_auto_translate.cpython-310.pyc differ diff --git a/__pycache__/x4B_integrate_manual_translates_to_master.cpython-310.pyc b/__pycache__/x4B_integrate_manual_translates_to_master.cpython-310.pyc index 858c170..dcb4951 100644 Binary files a/__pycache__/x4B_integrate_manual_translates_to_master.cpython-310.pyc and b/__pycache__/x4B_integrate_manual_translates_to_master.cpython-310.pyc differ diff --git a/__pycache__/x4_integrate_translates_to_master.cpython-310.pyc b/__pycache__/x4_integrate_translates_to_master.cpython-310.pyc index 21db771..8c453ad 100644 Binary files a/__pycache__/x4_integrate_translates_to_master.cpython-310.pyc and b/__pycache__/x4_integrate_translates_to_master.cpython-310.pyc differ diff --git a/__pycache__/x6_update_from_master.cpython-310.pyc b/__pycache__/x6_update_from_master.cpython-310.pyc index 755bedb..05ab2e8 100644 Binary files a/__pycache__/x6_update_from_master.cpython-310.pyc and b/__pycache__/x6_update_from_master.cpython-310.pyc differ diff --git a/translation_config.json b/translation_config.json index 3b25b5a..c621fd1 100644 --- a/translation_config.json +++ b/translation_config.json @@ -1,12 +1,12 @@ { "codigo_tipo_PLC": "allenbradley", - "codigo_columna_maestra": "es-ES", - "codigo_idioma_seleccionado": "en-US", - "codigo_idioma_secundario": "es-ES", - "work_dir": "C:/Trabajo/VM/33 - 9.4022 - Shibuya - Usa/Reporte/Language", + "codigo_columna_maestra": "en-US", + "codigo_idioma_seleccionado": "it-IT", + "codigo_idioma_secundario": "en-US", + "work_dir": "C:/Trabajo/VM/35 - 9.4023 - Shibuya - Mayo - Usa/Reporte/Language", "master_name": "1_hmi_master_translates_allenbradley.xlsx", - "translate_name": "2_master_export2translate_allenbradley_en-US.xlsx", - "auto_translate_name": "3_master_export2translate_translated_allenbradley_en-US.xlsx", + "translate_name": "2_master_export2translate_allenbradley_it-IT.xlsx", + "auto_translate_name": "3_master_export2translate_translated_allenbradley_it-IT.xlsx", "nivel_afinidad_minimo": 0.5, "traducir_todo": false } \ No newline at end of file diff --git a/x2_master_export2translate.py b/x2_master_export2translate.py index 19b7fe2..d9b242c 100644 --- a/x2_master_export2translate.py +++ b/x2_master_export2translate.py @@ -6,9 +6,12 @@ import langid from openpyxl import load_workbook from openpyxl.styles import PatternFill, Alignment, Font from collections import defaultdict +from openai import OpenAI +from openai_api_key import openai_api_key # Definir el logger a nivel de módulo logger = None +openai_client = OpenAI(api_key=openai_api_key()) def configurar_detector_idiomas(): @@ -41,67 +44,145 @@ def exportar_para_traduccion(config: TranslationConfig): return configurar_detector_idiomas() - df_maestro = fc.read_dataframe_with_cleanup_retries(master_path) df_export = pd.DataFrame() primera_columna = df_maestro.columns[0] df_export[primera_columna] = df_maestro[primera_columna] - df_export[config.codigo_idioma_seleccionado] = df_maestro[ - config.codigo_idioma_seleccionado - ] + + columna_propuesta = f"{config.codigo_idioma_seleccionado}_Propuesto" + df_export[columna_propuesta] = df_maestro[config.codigo_idioma_seleccionado] + + # Add validation columns if source and target languages are different + if config.codigo_columna_maestra != config.codigo_idioma_seleccionado: + df_export["Validation_Error"] = "" + df_export["Affinity_Score"] = None + df_export["Idioma_Detectado"] = "" + # Agregar columna del idioma secundario + if config.codigo_idioma_secundario in df_maestro.columns: + df_export[config.codigo_idioma_secundario] = df_maestro[ + config.codigo_idioma_secundario + ] + ruta_export = config.get_translate_path() with pd.ExcelWriter(ruta_export, engine="openpyxl") as writer: df_export.to_excel(writer, index=False, sheet_name="Sheet1") - workbook = writer.book worksheet = writer.sheets["Sheet1"] + # Inmovilizar paneles en A2 + worksheet.freeze_panes = 'A2' + wrap_alignment = Alignment(wrap_text=True, vertical="top") - for col in ["A", "B"]: - for cell in worksheet[col]: - cell.alignment = wrap_alignment - worksheet.column_dimensions[col].width = 50 + for col in worksheet.columns: + max_length = 0 + column = col[0].column_letter - idioma_esperado = fc.idiomas_shortcodefromcode( - config.codigo_idioma_seleccionado + for cell in col: + try: + if cell.value: + text_length = len(str(cell.value)) + if text_length > 50: + cell.alignment = wrap_alignment + text_length = min( + 50, max(len(word) for word in str(cell.value).split()) + ) + max_length = max(max_length, text_length) + except: + pass + + adjusted_width = min(50, max_length + 2) + worksheet.column_dimensions[column].width = ( + adjusted_width if adjusted_width > 8 else 8 + ) + + red_fill = PatternFill( + start_color="FF0000", end_color="FF0000", fill_type="solid" + ) + yellow_fill = PatternFill( + start_color="FFFF00", end_color="FFFF00", fill_type="solid" + ) + blue_fill = PatternFill( + start_color="ADD8E6", end_color="ADD8E6", fill_type="solid" ) - fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid") - bold_font = Font(bold=True) - total_rows = worksheet.max_row - 1 # Excluimos la fila de encabezado + total_rows = worksheet.max_row - 1 progress_bar = fc.ProgressBar( total_rows, prefix="Procesando filas:", suffix="Completado" ) - print("Iniciando procesamiento de filas...") - texto_a_filas = defaultdict(list) + inconsistencias = 0 + afinidad_baja = 0 + for row in range(2, worksheet.max_row + 1): texto = worksheet.cell(row=row, column=2).value if texto: + # Language detection texto_limpio = fc.limpiar_texto(config.codigo_tipo_PLC, texto) - - # Solo considerar para duplicados si el texto limpio es igual al original if texto == texto_limpio: texto_a_filas[texto].append(row) - # Detectar idioma y marcar si es incorrecto idioma_detectado = detectar_idioma(texto, config.codigo_tipo_PLC) + idioma_esperado = fc.idiomas_shortcodefromcode( + config.codigo_idioma_seleccionado + ) if ( idioma_detectado != "unknown" and idioma_detectado != idioma_esperado ): - worksheet.cell(row=row, column=2).fill = fill + worksheet.cell(row=row, column=2).fill = blue_fill nombre_idioma = obtener_nombre_idioma(idioma_detectado) - worksheet.cell(row=row, column=3).value = nombre_idioma + worksheet.cell( + row=row, + column=df_export.columns.get_loc("Idioma_Detectado") + 1, + ).value = nombre_idioma + + # Validation checks for different languages + if config.codigo_columna_maestra != config.codigo_idioma_seleccionado: + texts_to_check = {} + batch_size = 20 + + for row in range(2, worksheet.max_row + 1): + clave = worksheet.cell(row=row, column=1).value + texto = worksheet.cell(row=row, column=2).value + if pd.notnull(texto) and texto.strip() != "": + texts_to_check[clave] = texto + + if len(texts_to_check) >= batch_size: + try: + affinities = fc.affinity_batch_openai( + config.codigo_tipo_PLC, + texts_to_check, + openai_client, + logger, + ) + for check_row, (key, score) in enumerate( + affinities.items(), start=2 + ): + col_idx = ( + df_export.columns.get_loc("Affinity_Score") + + 1 + ) + worksheet.cell( + row=check_row, column=col_idx + ).value = score + if score < 1: + worksheet.cell( + row=check_row, column=2 + ).fill = yellow_fill + afinidad_baja += 1 + except Exception as e: + logger.error(f"Error en lote de afinidad: {str(e)}") + texts_to_check.clear() progress_bar.increment() - # Marcar celdas duplicadas en negrita + # Mark duplicate cells in bold + bold_font = Font(bold=True) celdas_duplicadas = 0 for filas in texto_a_filas.values(): if len(filas) > 1: @@ -118,7 +199,17 @@ def exportar_para_traduccion(config: TranslationConfig): "Se ha añadido el nombre del idioma detectado cuando es diferente del esperado." ) print( - f"Se han marcado {celdas_duplicadas} celdas en negrita por tener texto duplicado en la columna del idioma seleccionado." + f"Se ha agregado la columna del idioma secundario ({config.codigo_idioma_secundario}) al final de la planilla." + ) + if config.codigo_columna_maestra != config.codigo_idioma_seleccionado: + print( + f"Se encontraron {inconsistencias} celdas con errores de validación (marcadas en rojo)" + ) + print( + f"Se encontraron {afinidad_baja} celdas con afinidad menor a 1 (marcadas en amarillo)" + ) + print( + f"Se han marcado {celdas_duplicadas} celdas en negrita por tener texto duplicado." ) diff --git a/x3_llm_auto_translate.py b/x3_llm_auto_translate.py index 8a63d3a..b4975f2 100644 --- a/x3_llm_auto_translate.py +++ b/x3_llm_auto_translate.py @@ -11,10 +11,9 @@ import html from tqdm import tqdm import PyLibrary.funciones_comunes as fc import time -import PyLibrary.funciones_comunes as fc from translation_config import TranslationConfig -from openai import OpenAI -from tqdm import tqdm +from openpyxl.styles import PatternFill, Alignment +import sys openai_client = OpenAI(api_key=openai_api_key()) GOOGLE_APPLICATION_CREDENTIALS = "translate-431108-020c17463fbb.json" @@ -105,109 +104,11 @@ def translate_batch_openai(texts_dict, source_lang, target_lang): return dict(zip(texts_dict.keys(), translations)) -def affinity_batch_openai(codigo_tipo_PLC, texts_dict): - system_prompt = ( - "Evaluate the semantic similarity between the following table of pairs of texts in json format on a scale from 0 to 1. " - "Return the similarity scores for every row in JSON format as a list of numbers, without any additional text or formatting." - ) - original_list = [ - fc.compactar_celda_traducida(codigo_tipo_PLC, key) for key in texts_dict.keys() - ] - re_translated_list = list(texts_dict.values()) - - request_payload = json.dumps( - {"original": original_list, "compared": re_translated_list} - ) - logger.info(f"Solicitando Afinidad para el lote de textos:\n{request_payload}") - - response = openai_client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - { - "role": "system", - "content": system_prompt, - }, - {"role": "user", "content": request_payload}, - ], - max_tokens=1500, - temperature=0.3, - ) - response_content = response.choices[0].message.content - - # Limpiar y convertir el contenido de la respuesta - cleaned_response_content = response_content.strip().strip("'```json").strip("```") - - # Intentar convertir el contenido a JSON - try: - response_payload = json.loads(cleaned_response_content) - except json.JSONDecodeError: - raise ValueError("La respuesta no se pudo decodificar como JSON.") - - # Manejar diferentes formatos de respuesta - if isinstance(response_payload, dict) and "similarity_scores" in response_payload: - scores = response_payload["similarity_scores"] - elif isinstance(response_payload, list): - scores = response_payload - else: - raise ValueError("Formato de respuesta inesperado.") - - logger.info(f"Respuestas recibidas:\n{scores}") - - if len(scores) != len(original_list): - raise ValueError( - "La cantidad de afinidades recibidas no coincide con la cantidad de textos enviados." - ) - - return dict(zip(texts_dict.keys(), scores)) - - -# Función que calcula la afinidad entre dos textos -def calcular_afinidad(tipo_PLC, texto1, texto2): - system_prompt = ( - "Evaluate the semantic similarity between the following pair of texts on a scale from 0 to 1. " - "Return the similarity score as a single number." - ) - - original_text = fc.compactar_celda_traducida(tipo_PLC, texto1) - compared_text = texto2 - - request_payload = json.dumps({"original": original_text, "compared": compared_text}) - logger.info(f"Solicitando afinidad para el par de textos:\n{request_payload}") - - response = openai_client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - { - "role": "system", - "content": system_prompt, - }, - {"role": "user", "content": request_payload}, - ], - max_tokens=1500, - temperature=0.3, - ) - response_content = response.choices[0].message.content - - # Limpiar y convertir el contenido de la respuesta - cleaned_response_content = response_content.strip().strip("'```json").strip("```") - - # Intentar convertir el contenido a JSON - try: - score = float(cleaned_response_content) - except ValueError: - raise ValueError( - f"La respuesta no se pudo decodificar como un número: {cleaned_response_content}" - ) - - return score - - def main(config: TranslationConfig): - global logger df = fc.read_dataframe_with_cleanup_retries(config.get_translate_path()) source_col = config.codigo_columna_maestra - source_translated_col = config.codigo_idioma_seleccionado + source_translated_col = f"{config.codigo_idioma_seleccionado}_Propuesto" target_col = f"{config.codigo_idioma_seleccionado} Translated" check_translate_col = f"{config.codigo_idioma_seleccionado} CheckTranslate" affinity_col = f"{config.codigo_idioma_seleccionado} Affinity" @@ -220,22 +121,39 @@ def main(config: TranslationConfig): texts_to_translate = {} # Inicializar ProgressBar para la fase de preparación - prep_progress = fc.ProgressBar(len(df), prefix='Preparando textos:', suffix='Completado') - + prep_progress = fc.ProgressBar( + len(df), prefix="Preparando textos:", suffix="Completado" + ) + for index, row in df.iterrows(): celda_clave = str(row[source_col]) - source_translated_text = str(row[source_translated_col]) if source_translated_col in df.columns else "" - celda_clave_compactada = fc.compactar_celda_traducida(config.codigo_tipo_PLC, celda_clave) + source_translated_text = ( + str(row[source_translated_col]) + if source_translated_col in df.columns + else "" + ) + celda_clave_compactada = fc.compactar_celda_traducida( + config.codigo_tipo_PLC, celda_clave + ) if config.traducir_todo: - if fc.texto_requiere_traduccion(config.codigo_tipo_PLC, celda_clave_compactada, logger): + if fc.texto_requiere_traduccion( + config.codigo_tipo_PLC, celda_clave_compactada, logger + ): df.at[index, source_translated_col] = "" texts_to_translate[celda_clave] = celda_clave_compactada else: - if pd.isna(row[source_translated_col]) or source_translated_text.strip() == "": - if fc.texto_requiere_traduccion(config.codigo_tipo_PLC, celda_clave_compactada, logger) or fc.texto_con_campos_especiales(config.codigo_tipo_PLC, celda_clave_compactada): + if ( + pd.isna(row[source_translated_col]) + or source_translated_text.strip() == "" + ): + if fc.texto_requiere_traduccion( + config.codigo_tipo_PLC, celda_clave_compactada, logger + ) or fc.texto_con_campos_especiales( + config.codigo_tipo_PLC, celda_clave_compactada + ): texts_to_translate[celda_clave] = celda_clave_compactada - + prep_progress.update(index + 1) prep_progress.finish() @@ -245,14 +163,16 @@ def main(config: TranslationConfig): print(f"\nNúmero total de textos a traducir: {num_texts}") # Inicializar ProgressBar para la fase de traducción - trans_progress = fc.ProgressBar(num_texts, prefix='Traduciendo:', suffix='Completado') + trans_progress = fc.ProgressBar( + num_texts, prefix="Traduciendo:", suffix="Completado" + ) # Traducciones translations = {} for start_idx in range(0, num_texts, batch_size): end_idx = min(start_idx + batch_size, num_texts) batch_texts = dict(list(texts_to_translate.items())[start_idx:end_idx]) - logger.info(f"Traduciendo: celdas desde {start_idx} a {end_idx}.") + logger.info(f"Traduciendo: celdas desde {start_idx} a {end_idx}.") retries = 4 for attempt in range(retries): @@ -260,26 +180,36 @@ def main(config: TranslationConfig): batch_translations = translate_batch_openai( batch_texts, fc.idiomas_idiomafromcode(config.codigo_columna_maestra), - fc.idiomas_idiomafromcode(config.codigo_idioma_seleccionado) + fc.idiomas_idiomafromcode(config.codigo_idioma_seleccionado), ) translations.update(batch_translations) break except Exception as e: if attempt < retries - 1: - logger.warning(f"Error en el intento {attempt + 1} de traducción de celdas desde {start_idx} a {end_idx}: {e}. Reintentando...") - print(f"Error en el intento {attempt + 1} de traducción de celdas desde {start_idx} a {end_idx}: {e}. Reintentando...") + logger.warning( + f"Error en el intento {attempt + 1} de traducción de celdas desde {start_idx} a {end_idx}: {e}. Reintentando..." + ) + print( + f"Error en el intento {attempt + 1} de traducción de celdas desde {start_idx} a {end_idx}: {e}. Reintentando..." + ) time.sleep(3) else: - logger.error(f"Error en todos los intentos de traducción de celdas desde {start_idx} a {end_idx}: {e}") - print(f"Error en todos los intentos de traducción de celdas desde {start_idx} a {end_idx}: {e}") - + logger.error( + f"Error en todos los intentos de traducción de celdas desde {start_idx} a {end_idx}: {e}" + ) + print( + f"Error en todos los intentos de traducción de celdas desde {start_idx} a {end_idx}: {e}" + ) + trans_progress.update(end_idx) trans_progress.finish() logger.info(f"Número total de traducciones recibidas: {len(translations)}") # Inicializar ProgressBar para la fase de actualización del DataFrame - update_progress = fc.ProgressBar(len(df), prefix='Actualizando DataFrame:', suffix='Completado') + update_progress = fc.ProgressBar( + len(df), prefix="Actualizando DataFrame:", suffix="Completado" + ) # Actualizar el DataFrame con las traducciones y hacemos la Traduccion inversa for index, row in df.iterrows(): @@ -289,52 +219,78 @@ def main(config: TranslationConfig): try: google_translation = google_translate( translations[celda_clave], - fc.idiomas_shortcodefromcode(config.codigo_columna_maestra) + fc.idiomas_shortcodefromcode(config.codigo_columna_maestra), ) df.at[index, check_translate_col] = google_translation except Exception as e: - logger.error(f"Error en la traducción de Google para el texto '{celda_clave}': {e}") + logger.error( + f"Error en la traducción de Google para el texto '{celda_clave}': {e}" + ) df.at[index, check_translate_col] = "Error en la traducción" df.at[index, affinity_col] = 0.0 update_progress.increment() update_progress.finish() + # Inicializar ProgressBar para la fase de cálculo de afinidad - affinity_progress = fc.ProgressBar(num_texts, prefix='Calculando afinidad:', suffix='Completado') + affinity_progress = fc.ProgressBar( + num_texts, prefix="Calculando afinidad:", suffix="Completado" + ) # Afinidades affinities = {} for start_idx in range(0, num_texts, batch_size): end_idx = min(start_idx + batch_size, num_texts) batch_texts = dict(list(texts_to_translate.items())[start_idx:end_idx]) - logger.info(f"Afinidad: celdas desde {start_idx} a {end_idx}.") + logger.info(f"Afinidad: celdas desde {start_idx} a {end_idx}.") retries = 2 for attempt in range(retries): try: - batch_affinities = affinity_batch_openai(config.codigo_tipo_PLC, batch_texts) + batch_affinities = fc.affinity_batch_openai( + config.codigo_tipo_PLC, batch_texts, openai_client, logger + ) affinities.update(batch_affinities) break except Exception as e: if attempt < retries - 1: - logger.warning(f"Error en el intento {attempt + 1} de Afinidad de celdas desde {start_idx} a {end_idx}: {e}. Reintentando...") - print(f"Error en el intento {attempt + 1} de Afinidad de celdas desde {start_idx} a {end_idx}: {e}. Reintentando...") + logger.warning( + f"Error en el intento {attempt + 1} de Afinidad de celdas desde {start_idx} a {end_idx}: {e}. Reintentando..." + ) + print( + f"Error en el intento {attempt + 1} de Afinidad de celdas desde {start_idx} a {end_idx}: {e}. Reintentando..." + ) time.sleep(3) else: - logger.error(f"Error en todos los intentos de Afinidad de celdas desde {start_idx} a {end_idx}: {e}") - print(f"Error en todos los intentos de Afinidad de celdas desde {start_idx} a {end_idx}: {e}") + logger.error( + f"Error en todos los intentos de Afinidad de celdas desde {start_idx} a {end_idx}: {e}" + ) + print( + f"Error en todos los intentos de Afinidad de celdas desde {start_idx} a {end_idx}: {e}" + ) for key, value in batch_texts.items(): try: - score = calcular_afinidad(config.codigo_tipo_PLC, key, value) + score = fc.calcular_afinidad( + config.codigo_tipo_PLC, + key, + value, + openai_client, + logger, + ) affinities[key] = score except Exception as ind_e: affinities[key] = "0" - logger.error(f"Error en el cálculo individual de Afinidad para el texto '{key}': {ind_e}") - print(f"Error en el cálculo individual de Afinidad para el texto '{key}': {ind_e}") - + logger.error( + f"Error en el cálculo individual de Afinidad para el texto '{key}': {ind_e}" + ) + print( + f"Error en el cálculo individual de Afinidad para el texto '{key}': {ind_e}" + ) + affinity_progress.increment() affinity_progress.finish() + # Actualizar el DataFrame con las Afinidades for index, row in df.iterrows(): celda_clave = str(row[source_col]) @@ -342,10 +298,70 @@ def main(config: TranslationConfig): df.at[index, affinity_col] = affinities[celda_clave] output_path = config.get_auto_translate_path() - fc.save_dataframe_with_retries(df, output_path=output_path) + + with pd.ExcelWriter(output_path, engine="openpyxl") as writer: + df.to_excel(writer, index=False, sheet_name="Sheet1") + + workbook = writer.book + worksheet = writer.sheets["Sheet1"] + # Inmovilizar paneles en A2 + worksheet.freeze_panes = "A2" + + # Configurar ancho de columnas basado en contenido + from openpyxl.utils import get_column_letter + + for col in worksheet.columns: + max_length = 0 + column = col[0].column_letter + + for cell in col: + try: + if cell.value: + text_length = len(str(cell.value)) + # Si el texto es más largo que 50, aplicamos wrap_text + if text_length > 50: + cell.alignment = Alignment(wrap_text=True, vertical="top") + text_length = min( + 50, max(len(word) for word in str(cell.value).split()) + ) + max_length = max(max_length, text_length) + except: + pass + + # Ajustar el ancho con un pequeño padding + adjusted_width = min(50, max_length + 2) + worksheet.column_dimensions[column].width = ( + adjusted_width if adjusted_width > 8 else 8 + ) + + # Colores para el formato condicional + light_blue = PatternFill( + start_color="ADD8E6", end_color="ADD8E6", fill_type="solid" + ) + yellow = PatternFill( + start_color="FFFF00", end_color="FFFF00", fill_type="solid" + ) + + # Aplicar formatos + for row in worksheet.iter_rows(min_row=2): + translated_cell = row[df.columns.get_loc(target_col)] + if translated_cell.value: + affinity_cell = row[df.columns.get_loc(affinity_col)] + try: + affinity_value = float( + affinity_cell.value if affinity_cell.value else 0 + ) + if affinity_value == 1: + translated_cell.fill = light_blue + elif affinity_value < 1: + translated_cell.fill = yellow + except (ValueError, TypeError): + pass + logger.info(f"Archivo traducido guardado en: {output_path}") print(f"\nArchivo traducido guardado en: {output_path}") + def run(config: TranslationConfig): global logger logger = fc.configurar_logger(config.work_dir) @@ -353,6 +369,8 @@ def run(config: TranslationConfig): print(f"\rIniciando: {script_name}\r") main(config) + if __name__ == "__main__": import menu_pasos_traduccion - menu_pasos_traduccion.main() \ No newline at end of file + + menu_pasos_traduccion.main() diff --git a/x4B_integrate_manual_translates_to_master.py b/x4B_integrate_manual_translates_to_master.py index fdd6a08..03ef62f 100644 --- a/x4B_integrate_manual_translates_to_master.py +++ b/x4B_integrate_manual_translates_to_master.py @@ -18,6 +18,7 @@ def importar_traduccion_manual(config: TranslationConfig): archivo_traduccion = config.get_translate_path() master_col = config.codigo_idioma_seleccionado + propuesto_col = f"{config.codigo_idioma_seleccionado}_Propuesto" df_maestro = fc.read_dataframe_with_cleanup_retries(archivo_maestro) df_traduccion = fc.read_dataframe_with_cleanup_retries(archivo_traduccion) @@ -28,7 +29,7 @@ def importar_traduccion_manual(config: TranslationConfig): for index, fila in df_traduccion.iterrows(): clave = fila[df_maestro.columns[0]] if clave in df_maestro[df_maestro.columns[0]].values: - valor_traducido = fila[master_col] + valor_traducido = fila[propuesto_col] # Use propuesto column valor_original = df_maestro.loc[ df_maestro[df_maestro.columns[0]] == clave, master_col ].values[0] @@ -38,6 +39,7 @@ def importar_traduccion_manual(config: TranslationConfig): and valor_traducido != "" and str(valor_original) != str(valor_traducido) ): + okToSave, Error = fc.verificar_celda_traducida( config.codigo_tipo_PLC, clave, valor_traducido ) @@ -62,7 +64,7 @@ def importar_traduccion_manual(config: TranslationConfig): f"Fila {index}, Columna {master_col}: No actualizado porque: {Error}" ) fila_excel = index + 2 - columna_excel = df_traduccion.columns.get_loc(master_col) + 1 + columna_excel = df_traduccion.columns.get_loc(propuesto_col) + 1 celdas_con_errores[(fila_excel, columna_excel)] = Error fc.save_dataframe_with_retries(df_maestro, output_path=archivo_maestro) diff --git a/x4_integrate_translates_to_master.py b/x4_integrate_translates_to_master.py index 17caf09..3b405dc 100644 --- a/x4_integrate_translates_to_master.py +++ b/x4_integrate_translates_to_master.py @@ -4,6 +4,7 @@ import os from translation_config import TranslationConfig from openpyxl import load_workbook from openpyxl.styles import Font +from openpyxl.styles import PatternFill, Alignment, Font # Definir el logger a nivel de módulo logger = None @@ -16,6 +17,7 @@ def importar_traduccion(config: TranslationConfig): return master_col = config.codigo_idioma_seleccionado + master_propuesto_col = f"{master_col}_Propuesto" translated_col = f"{config.codigo_idioma_seleccionado} Translated" affinity_col = f"{config.codigo_idioma_seleccionado} Affinity" @@ -24,47 +26,106 @@ def importar_traduccion(config: TranslationConfig): config.get_auto_translate_path() ) + # Caso especial: columna maestra es igual al idioma seleccionado + is_same_column = config.codigo_columna_maestra == config.codigo_idioma_seleccionado + + # Si es el mismo, asegurarse que existe la columna propuesta + if is_same_column and master_propuesto_col not in df_maestro.columns: + df_maestro[master_propuesto_col] = df_maestro[master_col] + celdas_modificadas = {} + if config.codigo_columna_maestra not in df_maestro.columns: + print(f"Error: Columnas requeridas no encontradas en el archivo maestro") + return + + if ( + config.codigo_columna_maestra not in df_traduccion.columns + or translated_col not in df_traduccion.columns + or affinity_col not in df_traduccion.columns + ): + print(f"Error: Columnas requeridas no encontradas en el archivo de traducción") + return + for index, fila in df_traduccion.iterrows(): - clave = fila[df_maestro.columns[0]] - if clave in df_maestro[df_maestro.columns[0]].values: + clave = fila[config.codigo_columna_maestra] + mascara = df_maestro[config.codigo_columna_maestra] == clave + + if mascara.any(): if ( - fila[affinity_col] >= config.nivel_afinidad_minimo - and pd.notnull(fila[translated_col]) - and fila[translated_col] != "" + pd.notna(fila[affinity_col]) + and fila[affinity_col] >= config.nivel_afinidad_minimo + and pd.notna(fila[translated_col]) ): valor_traducido = fila[translated_col] - valor_original = df_maestro.loc[ - df_maestro[df_maestro.columns[0]] == clave, master_col - ].values[0] + indice_maestro = df_maestro.index[mascara].tolist()[0] + + # Determinar la columna a actualizar según el caso + columna_destino = master_propuesto_col if is_same_column else master_col + valor_original = df_maestro.loc[indice_maestro, columna_destino] if str(valor_original) != str(valor_traducido): - df_maestro.loc[ - df_maestro[df_maestro.columns[0]] == clave, master_col - ] = valor_traducido + df_maestro.loc[indice_maestro, columna_destino] = valor_traducido logger.info( - f'Fila {index}, Columna {translated_col}: "{valor_original}" actualizado a "{valor_traducido}"' + f'Fila {index}, Columna {columna_destino}: "{valor_original}" actualizado a "{valor_traducido}"' ) - fila_excel = ( - df_maestro.index[ - df_maestro[df_maestro.columns[0]] == clave - ].tolist()[0] - + 2 - ) - columna_excel = df_maestro.columns.get_loc(master_col) + 1 + fila_excel = indice_maestro + 2 + columna_excel = df_maestro.columns.get_loc(columna_destino) + 1 celdas_modificadas[(fila_excel, columna_excel)] = valor_traducido - else : - logger.error( - f'Clave {clave} no encontrada en master.' + + # Guardar con formato Excel + with pd.ExcelWriter(archivo_maestro, engine="openpyxl") as writer: + df_maestro.to_excel(writer, index=False, sheet_name="Sheet1") + + workbook = writer.book + worksheet = writer.sheets["Sheet1"] + + # Inmovilizar paneles en A2 + worksheet.freeze_panes = "A2" + + # Configurar ancho de columnas basado en contenido + from openpyxl.utils import get_column_letter + from openpyxl.styles import Alignment, Font, PatternFill + + for col in worksheet.columns: + max_length = 0 + column = col[0].column_letter + + for cell in col: + try: + if cell.value: + text_length = len(str(cell.value)) + if text_length > 50: + cell.alignment = Alignment(wrap_text=True, vertical="top") + text_length = min( + 50, max(len(word) for word in str(cell.value).split()) + ) + max_length = max(max_length, text_length) + except: + pass + + adjusted_width = min(50, max_length + 2) + worksheet.column_dimensions[column].width = ( + adjusted_width if adjusted_width > 8 else 8 ) - fc.save_dataframe_with_retries(df_maestro, output_path=archivo_maestro) + # Aplicar negrita y color a las celdas modificadas + light_blue = PatternFill( + start_color="ADD8E6", end_color="ADD8E6", fill_type="solid" + ) + for (fila, columna), valor in celdas_modificadas.items(): + celda = worksheet.cell(row=fila, column=columna) + celda.font = Font(bold=True) + celda.value = valor + celda.fill = light_blue - aplicar_negrita_celdas_modificadas(archivo_maestro, celdas_modificadas) - - print(f"Traducciones importadas y archivo maestro actualizado: {archivo_maestro}.") + if celdas_modificadas: + print( + f"Se han actualizado y marcado {len(celdas_modificadas)} celdas en el archivo maestro." + ) + else: + print("No se realizaron modificaciones en el archivo maestro.") def aplicar_negrita_celdas_modificadas(archivo, celdas_modificadas): diff --git a/x6_update_from_master.py b/x6_update_from_master.py index ec5cc02..e376a7d 100644 --- a/x6_update_from_master.py +++ b/x6_update_from_master.py @@ -16,91 +16,139 @@ def update_from_master(config: TranslationConfig, archivo_to_update): print("El archivo maestro no existe.") return - logger.info(" .... ") - logger.info( - f"Iniciando actualización en {archivo_to_update} desde el archivo maestro. Para {config.codigo_idioma_seleccionado}" - ) + logger.info(f"Iniciando actualización en {archivo_to_update} desde el archivo maestro. Para {config.codigo_idioma_seleccionado}") df_maestro = fc.read_dataframe_with_cleanup_retries(archivo_maestro) df_to_update = fc.read_dataframe_with_cleanup_retries(archivo_to_update) + # Create copy for changes tracking + df_changes = df_to_update.copy() + df_changes["Original_Value"] = "" + col_clave = config.codigo_columna_maestra + # Si la columna maestra es igual al idioma seleccionado, usamos la columna propuesta + is_same_column = config.codigo_columna_maestra == config.codigo_idioma_seleccionado + master_col = f"{config.codigo_idioma_seleccionado}_Propuesto" if is_same_column else config.codigo_idioma_seleccionado + + if master_col not in df_maestro.columns: + print(f"Error: Columna {master_col} no encontrada en el archivo maestro") + return + + celdas_modificadas = [] - celdas_con_errores = {} - celdas_vacias = {} - - progress_bar = fc.ProgressBar( - len(df_to_update), prefix="Actualizando filas:", suffix="Completado" - ) + progress_bar = fc.ProgressBar(len(df_to_update), prefix="Actualizando filas:", suffix="Completado") for index, fila in df_to_update.iterrows(): valor_original = fila[col_clave] clave = fc.compactar_celda_clave(config.codigo_tipo_PLC, valor_original) if not pd.isna(clave) and clave in df_maestro[col_clave].values: - # logger.info(f"Fila {index} : Clave: {clave}") - - indice_maestro = df_maestro.index[df_maestro[col_clave] == clave].tolist()[ - 0 - ] - valor_traducido_compacto = df_maestro.loc[ - indice_maestro, config.codigo_idioma_seleccionado - ] + indice_maestro = df_maestro.index[df_maestro[col_clave] == clave].tolist()[0] + valor_traducido_compacto = df_maestro.loc[indice_maestro, master_col] if pd.isna(valor_traducido_compacto): - celdas_vacias[indice_maestro] = "Celda vacía en el archivo maestro" - logger.info(f"Fila {index}: Celda vacía en el archivo maestro") - else: - valor_traducido = fc.decompactar_celda_traducida( - config.codigo_tipo_PLC, - celda_original=valor_original, - celda_traducida=valor_traducido_compacto, + continue + + valor_traducido = fc.decompactar_celda_traducida( + config.codigo_tipo_PLC, + celda_original=valor_original, + celda_traducida=valor_traducido_compacto + ) + + if not pd.isna(valor_traducido) and fila[config.codigo_idioma_seleccionado] != valor_traducido: + okToSave, Error = fc.verificar_celda_traducida( + config.codigo_tipo_PLC, clave, valor_traducido_compacto ) - if ( - not pd.isna(valor_traducido) - and fila[config.codigo_idioma_seleccionado] != valor_traducido - ): - okToSave, Error = fc.verificar_celda_traducida( - config.codigo_tipo_PLC, clave, valor_traducido_compacto - ) - if okToSave: - logger.info(f"Actualizado: Fila {index} : Clave: {clave}") - df_to_update.at[index, config.codigo_idioma_seleccionado] = ( - valor_traducido - ) - else: - df_to_update.at[index, config.codigo_idioma_seleccionado] = ( - valor_original - ) - logger.error(f"No actualizado porque: {Error}") - celdas_con_errores[indice_maestro] = Error + if okToSave: + # Store original value in changes DataFrame + df_changes.at[index, "Original_Value"] = fila[config.codigo_idioma_seleccionado] + + # Update both DataFrames + df_to_update.at[index, config.codigo_idioma_seleccionado] = valor_traducido + df_changes.at[index, config.codigo_idioma_seleccionado] = valor_traducido + + celdas_modificadas.append(index) + logger.info(f"Actualizado: Fila {index} : Clave: {clave}") progress_bar.increment() progress_bar.finish() + # Save updated file with formatting nombre, extension = os.path.splitext(archivo_to_update) nuevo_nombre = f"{nombre}_import{extension}" - fc.save_dataframe_with_retries(df_to_update, output_path=nuevo_nombre) + + with pd.ExcelWriter(nuevo_nombre, engine='openpyxl') as writer: + df_to_update.to_excel(writer, index=False) + + workbook = writer.book + worksheet = writer.sheets['Sheet1'] + + # Format columns + from openpyxl.utils import get_column_letter + from openpyxl.styles import Alignment, PatternFill + + for col in worksheet.columns: + max_length = 0 + column = col[0].column_letter + + for cell in col: + try: + if cell.value: + text_length = len(str(cell.value)) + if text_length > 50: + cell.alignment = Alignment(wrap_text=True, vertical='top') + text_length = min(50, max(len(word) for word in str(cell.value).split())) + max_length = max(max_length, text_length) + except: + pass + + adjusted_width = min(50, max_length + 2) + worksheet.column_dimensions[column].width = adjusted_width if adjusted_width > 8 else 8 - marcar_celdas_con_errores( - archivo_maestro, - celdas_con_errores, - celdas_vacias, - config.codigo_idioma_seleccionado, - ) + # Save changes file with highlighting + changes_nombre = f"{nombre}_changes{extension}" + if len(celdas_modificadas) > 0: + with pd.ExcelWriter(changes_nombre, engine="openpyxl") as writer: + df_changes.to_excel(writer, index=False) - print( - f"Se han actualizado las filas en {archivo_to_update} desde el archivo maestro." - ) - print( - f"Se han marcado {len(celdas_con_errores)} celdas con errores en el archivo maestro." - ) - print(f"Se han marcado {len(celdas_vacias)} celdas vacías en el archivo maestro.") + workbook = writer.book + worksheet = writer.sheets["Sheet1"] + + light_blue = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid") + + for row_idx in celdas_modificadas: + for col in range(1, len(df_changes.columns) + 1): + cell = worksheet.cell(row=row_idx + 2, column=col) + cell.fill = light_blue + + # Format columns in changes file too + for col in worksheet.columns: + max_length = 0 + column = col[0].column_letter + + for cell in col: + try: + if cell.value: + text_length = len(str(cell.value)) + if text_length > 50: + cell.alignment = Alignment(wrap_text=True, vertical='top') + text_length = min(50, max(len(word) for word in str(cell.value).split())) + max_length = max(max_length, text_length) + except: + pass + + adjusted_width = min(50, max_length + 2) + worksheet.column_dimensions[column].width = adjusted_width if adjusted_width > 8 else 8 + + print(f"Se han actualizado las filas en {archivo_to_update} desde el archivo maestro.") + print(f"Archivo de cambios guardado en: {changes_nombre}") + print(f"Se han marcado {len(celdas_modificadas)} filas modificadas.") logger.info(" .... ") - -def marcar_celdas_con_errores(archivo_maestro, celdas_con_errores, celdas_vacias, target_lang_code): +def marcar_celdas_con_errores( + archivo_maestro, celdas_con_errores, celdas_vacias, target_lang_code +): workbook = load_workbook(archivo_maestro) sheet = workbook.active @@ -112,8 +160,12 @@ def marcar_celdas_con_errores(archivo_maestro, celdas_con_errores, celdas_vacias print(f"No se encontró la columna para el idioma {target_lang_code}") return - error_fill = PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid") - empty_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") + error_fill = PatternFill( + start_color="FF0000", end_color="FF0000", fill_type="solid" + ) + empty_fill = PatternFill( + start_color="FFFF00", end_color="FFFF00", fill_type="solid" + ) white_font = Font(color="FFFFFF") # Fuente blanca para celdas con fondo rojo for indice_maestro, mensaje_error in celdas_con_errores.items(): @@ -132,7 +184,9 @@ def marcar_celdas_con_errores(archivo_maestro, celdas_con_errores, celdas_vacias cell.comment = comment workbook.save(archivo_maestro) - print(f"Se han marcado las celdas con errores y vacías en el archivo maestro {archivo_maestro}") + print( + f"Se han marcado las celdas con errores y vacías en el archivo maestro {archivo_maestro}" + ) def run(config: TranslationConfig, archivo_to_update):