Compare commits

..

No commits in common. "f2a410485b19c566c35082ecdd4f81f6d7012094" and "39116ca425e36f42325a10ae76b7e30a29d328e9" have entirely different histories.

12 changed files with 302 additions and 497 deletions

View File

@ -1,12 +1,12 @@
{ {
"codigo_tipo_PLC": "siemens", "codigo_tipo_PLC": "allenbradley",
"codigo_columna_maestra": "it-IT", "codigo_columna_maestra": "es-ES",
"codigo_idioma_seleccionado": "es-ES", "codigo_idioma_seleccionado": "en-US",
"codigo_idioma_secundario": "en-GB", "codigo_idioma_secundario": "es-ES",
"work_dir": "C:/Trabajo/VM/38 - 93998 - Sipa - PortoRico/Reporte/Language", "work_dir": "C:/Trabajo/VM/33 - 9.4022 - Shibuya - Usa/Reporte/Language",
"master_name": "1_hmi_master_translates_siemens.xlsx", "master_name": "1_hmi_master_translates_allenbradley.xlsx",
"translate_name": "2_master_export2translate_siemens_es-ES.xlsx", "translate_name": "2_master_export2translate_allenbradley_en-US.xlsx",
"auto_translate_name": "3_master_export2translate_translated_siemens_es-ES.xlsx", "auto_translate_name": "3_master_export2translate_translated_allenbradley_en-US.xlsx",
"nivel_afinidad_minimo": 0.5, "nivel_afinidad_minimo": 0.5,
"traducir_todo": false "traducir_todo": false
} }

View File

@ -6,7 +6,6 @@ import langid
from openpyxl import load_workbook from openpyxl import load_workbook
from openpyxl.styles import PatternFill, Alignment, Font from openpyxl.styles import PatternFill, Alignment, Font
from collections import defaultdict from collections import defaultdict
from openai_api_key import openai_api_key
# Definir el logger a nivel de módulo # Definir el logger a nivel de módulo
logger = None logger = None
@ -36,172 +35,73 @@ def obtener_nombre_idioma(codigo_corto):
def exportar_para_traduccion(config: TranslationConfig): def exportar_para_traduccion(config: TranslationConfig):
"""
Exporta los textos del archivo maestro para su traducción, realizando validaciones
y cálculos de afinidad en lotes. Los textos idénticos reciben afinidad 1 automáticamente.
"""
master_path = config.get_master_path() master_path = config.get_master_path()
if not os.path.exists(master_path): if not os.path.exists(master_path):
print("El archivo maestro no existe.") print("El archivo maestro no existe.")
return return
configurar_detector_idiomas() configurar_detector_idiomas()
df_maestro = fc.read_dataframe_with_cleanup_retries(master_path) df_maestro = fc.read_dataframe_with_cleanup_retries(master_path)
# Preparar DataFrame de exportación
df_export = pd.DataFrame() df_export = pd.DataFrame()
primera_columna = df_maestro.columns[0] primera_columna = df_maestro.columns[0]
df_export[primera_columna] = df_maestro[primera_columna] df_export[primera_columna] = df_maestro[primera_columna]
df_export[config.codigo_idioma_seleccionado] = df_maestro[
columna_propuesta = f"{config.codigo_idioma_seleccionado}_Propuesto" config.codigo_idioma_seleccionado
df_export[columna_propuesta] = df_maestro[config.codigo_idioma_seleccionado] ]
# Agregar columnas de validación si los idiomas son diferentes
if config.codigo_columna_maestra != config.codigo_idioma_seleccionado:
df_export["Validation_Error"] = ""
df_export["Affinity_Score"] = None
df_export["Idioma_Detectado"] = "" 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() ruta_export = config.get_translate_path()
with pd.ExcelWriter(ruta_export, engine="openpyxl") as writer: with pd.ExcelWriter(ruta_export, engine="openpyxl") as writer:
df_export.to_excel(writer, index=False, sheet_name="Sheet1") df_export.to_excel(writer, index=False, sheet_name="Sheet1")
workbook = writer.book workbook = writer.book
worksheet = writer.sheets["Sheet1"] worksheet = writer.sheets["Sheet1"]
# Inmovilizar paneles en A2
worksheet.freeze_panes = "A2"
# Configurar estilos
wrap_alignment = Alignment(wrap_text=True, vertical="top") wrap_alignment = Alignment(wrap_text=True, vertical="top")
red_fill = PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid") for col in ["A", "B"]:
yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") for cell in worksheet[col]:
blue_fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid")
# Ajustar anchos de columna
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 = wrap_alignment cell.alignment = wrap_alignment
text_length = min(50, max(len(word) for word in str(cell.value).split())) worksheet.column_dimensions[col].width = 50
max_length = max(max_length, text_length)
except:
pass
adjusted_width = min(50, max_length + 2) idioma_esperado = fc.idiomas_shortcodefromcode(
worksheet.column_dimensions[column].width = adjusted_width if adjusted_width > 8 else 8 config.codigo_idioma_seleccionado
# Primera fase: Procesar detección de idioma y recopilar textos para afinidad
texts_to_check = {} # Para textos que necesitan cálculo de afinidad
identical_texts = {} # Para textos idénticos (afinidad 1)
texto_a_filas = defaultdict(list)
inconsistencias = 0
afinidad_baja = 0
progress_bar = fc.ProgressBar(
worksheet.max_row - 1, prefix="Procesando textos:", suffix="Completado"
) )
fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid")
for row in range(2, worksheet.max_row + 1):
texto_original = worksheet.cell(row=row, column=1).value
texto_propuesto = worksheet.cell(row=row, column=2).value
if texto_original and texto_propuesto:
# Detección de idioma
texto_limpio = fc.limpiar_texto(config.codigo_tipo_PLC, texto_propuesto)
if texto_propuesto == texto_limpio:
texto_a_filas[texto_propuesto].append(row)
idioma_detectado = detectar_idioma(texto_propuesto, 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 = blue_fill
nombre_idioma = obtener_nombre_idioma(idioma_detectado)
worksheet.cell(
row=row,
column=df_export.columns.get_loc("Idioma_Detectado") + 1
).value = nombre_idioma
# Recopilar textos para afinidad si los idiomas son diferentes
if config.codigo_columna_maestra != config.codigo_idioma_seleccionado:
if pd.notnull(texto_propuesto) and texto_propuesto.strip() != "":
# Compactar los textos para comparación
texto_original_comp = fc.compactar_celda_traducida(config.codigo_tipo_PLC, str(texto_original))
texto_propuesto_comp = fc.compactar_celda_traducida(config.codigo_tipo_PLC, str(texto_propuesto))
# Si los textos son idénticos después de compactar, afinidad automática de 1
if texto_original_comp == texto_propuesto_comp:
identical_texts[texto_original] = row
else:
texts_to_check[texto_original] = texto_propuesto
progress_bar.increment()
progress_bar.finish()
# Configurar el modelo a usar
modelo_llm = fc.LLM_MODELS["OpenAI"] # o el que se prefiera
api_key = openai_api_key() # solo necesario para OpenAI y Grok
# Segunda fase: Procesar textos idénticos y calcular afinidades en lote
if config.codigo_columna_maestra != config.codigo_idioma_seleccionado:
# Asignar afinidad 1 a textos idénticos
logger.info(f"Asignando afinidad 1 a {len(identical_texts)} textos idénticos")
for _, row in identical_texts.items():
col_idx = df_export.columns.get_loc("Affinity_Score") + 1
worksheet.cell(row=row, column=col_idx).value = 1.0
# Calcular afinidades para textos diferentes
if texts_to_check:
logger.info(f"Calculando afinidad para {len(texts_to_check)} textos diferentes")
try:
# Calcular afinidades
affinities = fc.calcular_afinidad_batch(
texts_to_check,
config.codigo_tipo_PLC,
modelo_llm,
logger,
api_key
)
# Aplicar resultados de afinidad
progress_bar = fc.ProgressBar(
len(affinities), prefix="Aplicando afinidades:", suffix="Completado"
)
for texto_original, afinidad in affinities.items():
row = next(row for row in range(2, worksheet.max_row + 1)
if worksheet.cell(row=row, column=1).value == texto_original)
col_idx = df_export.columns.get_loc("Affinity_Score") + 1
worksheet.cell(row=row, column=col_idx).value = afinidad
if afinidad < 1:
worksheet.cell(row=row, column=2).fill = yellow_fill
afinidad_baja += 1
progress_bar.increment()
progress_bar.finish()
except Exception as e:
logger.error(f"Error en el cálculo de afinidad por lotes: {str(e)}")
print(f"Error en el cálculo de afinidad por lotes: {str(e)}")
# Marcar celdas duplicadas
bold_font = Font(bold=True) bold_font = Font(bold=True)
total_rows = worksheet.max_row - 1 # Excluimos la fila de encabezado
progress_bar = fc.ProgressBar(
total_rows, prefix="Procesando filas:", suffix="Completado"
)
print("Iniciando procesamiento de filas...")
texto_a_filas = defaultdict(list)
for row in range(2, worksheet.max_row + 1):
texto = worksheet.cell(row=row, column=2).value
if texto:
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)
if (
idioma_detectado != "unknown"
and idioma_detectado != idioma_esperado
):
worksheet.cell(row=row, column=2).fill = fill
nombre_idioma = obtener_nombre_idioma(idioma_detectado)
worksheet.cell(row=row, column=3).value = nombre_idioma
progress_bar.increment()
# Marcar celdas duplicadas en negrita
celdas_duplicadas = 0 celdas_duplicadas = 0
for filas in texto_a_filas.values(): for filas in texto_a_filas.values():
if len(filas) > 1: if len(filas) > 1:
@ -210,18 +110,17 @@ def exportar_para_traduccion(config: TranslationConfig):
cell.font = bold_font cell.font = bold_font
celdas_duplicadas += len(filas) celdas_duplicadas += len(filas)
# Imprimir resumen progress_bar.finish()
print(f"\nArchivo exportado para traducción: {ruta_export}") print(f"\nArchivo exportado para traducción: {ruta_export}")
print("Las celdas con idioma incorrecto han sido marcadas en azul.") print("Las celdas con idioma incorrecto han sido marcadas en azul.")
print("Se ha añadido el nombre del idioma detectado cuando es diferente del esperado.") print(
print(f"Se ha agregado la columna del idioma secundario ({config.codigo_idioma_secundario}) al final de la planilla.") "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."
)
if config.codigo_columna_maestra != config.codigo_idioma_seleccionado:
print(f"Se encontraron {len(identical_texts)} textos idénticos (afinidad 1)")
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.")
def run(config: TranslationConfig): def run(config: TranslationConfig):
global logger global logger

View File

@ -11,19 +11,18 @@ import html
from tqdm import tqdm from tqdm import tqdm
import PyLibrary.funciones_comunes as fc import PyLibrary.funciones_comunes as fc
import time import time
import PyLibrary.funciones_comunes as fc
from translation_config import TranslationConfig from translation_config import TranslationConfig
from openpyxl.styles import PatternFill, Alignment from openai import OpenAI
import sys from tqdm import tqdm
openai_client = OpenAI(api_key=openai_api_key())
GOOGLE_APPLICATION_CREDENTIALS = "translate-431108-020c17463fbb.json" GOOGLE_APPLICATION_CREDENTIALS = "translate-431108-020c17463fbb.json"
batch_size = 20 batch_size = 20
# Definir el logger a nivel de módulo # Definir el logger a nivel de módulo
logger = None logger = None
# Crear el cliente OpenAI
openai_client = OpenAI(api_key=openai_api_key())
def init_google_translate_client(): def init_google_translate_client():
if os.path.exists(GOOGLE_APPLICATION_CREDENTIALS): if os.path.exists(GOOGLE_APPLICATION_CREDENTIALS):
@ -106,11 +105,109 @@ def translate_batch_openai(texts_dict, source_lang, target_lang):
return dict(zip(texts_dict.keys(), translations)) 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): def main(config: TranslationConfig):
global logger
df = fc.read_dataframe_with_cleanup_retries(config.get_translate_path()) df = fc.read_dataframe_with_cleanup_retries(config.get_translate_path())
source_col = config.codigo_columna_maestra source_col = config.codigo_columna_maestra
source_translated_col = f"{config.codigo_idioma_seleccionado}_Propuesto" source_translated_col = config.codigo_idioma_seleccionado
target_col = f"{config.codigo_idioma_seleccionado} Translated" target_col = f"{config.codigo_idioma_seleccionado} Translated"
check_translate_col = f"{config.codigo_idioma_seleccionado} CheckTranslate" check_translate_col = f"{config.codigo_idioma_seleccionado} CheckTranslate"
affinity_col = f"{config.codigo_idioma_seleccionado} Affinity" affinity_col = f"{config.codigo_idioma_seleccionado} Affinity"
@ -123,37 +220,20 @@ def main(config: TranslationConfig):
texts_to_translate = {} texts_to_translate = {}
# Inicializar ProgressBar para la fase de preparación # Inicializar ProgressBar para la fase de preparación
prep_progress = fc.ProgressBar( prep_progress = fc.ProgressBar(len(df), prefix='Preparando textos:', suffix='Completado')
len(df), prefix="Preparando textos:", suffix="Completado"
)
for index, row in df.iterrows(): for index, row in df.iterrows():
celda_clave = str(row[source_col]) celda_clave = str(row[source_col])
source_translated_text = ( source_translated_text = str(row[source_translated_col]) if source_translated_col in df.columns else ""
str(row[source_translated_col]) celda_clave_compactada = fc.compactar_celda_traducida(config.codigo_tipo_PLC, celda_clave)
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 config.traducir_todo:
if fc.texto_requiere_traduccion( if fc.texto_requiere_traduccion(config.codigo_tipo_PLC, celda_clave_compactada, logger):
config.codigo_tipo_PLC, celda_clave_compactada, logger
):
df.at[index, source_translated_col] = "" df.at[index, source_translated_col] = ""
texts_to_translate[celda_clave] = celda_clave_compactada texts_to_translate[celda_clave] = celda_clave_compactada
else: else:
if ( if pd.isna(row[source_translated_col]) or source_translated_text.strip() == "":
pd.isna(row[source_translated_col]) 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):
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 texts_to_translate[celda_clave] = celda_clave_compactada
prep_progress.update(index + 1) prep_progress.update(index + 1)
@ -165,9 +245,7 @@ def main(config: TranslationConfig):
print(f"\nNúmero total de textos a traducir: {num_texts}") print(f"\nNúmero total de textos a traducir: {num_texts}")
# Inicializar ProgressBar para la fase de traducción # Inicializar ProgressBar para la fase de traducción
trans_progress = fc.ProgressBar( trans_progress = fc.ProgressBar(num_texts, prefix='Traduciendo:', suffix='Completado')
num_texts, prefix="Traduciendo:", suffix="Completado"
)
# Traducciones # Traducciones
translations = {} translations = {}
@ -182,26 +260,18 @@ def main(config: TranslationConfig):
batch_translations = translate_batch_openai( batch_translations = translate_batch_openai(
batch_texts, batch_texts,
fc.idiomas_idiomafromcode(config.codigo_columna_maestra), 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) translations.update(batch_translations)
break break
except Exception as e: except Exception as e:
if attempt < retries - 1: if attempt < retries - 1:
logger.warning( logger.warning(f"Error en el intento {attempt + 1} de traducción de celdas desde {start_idx} a {end_idx}: {e}. Reintentando...")
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...")
)
print(
f"Error en el intento {attempt + 1} de traducción de celdas desde {start_idx} a {end_idx}: {e}. Reintentando..."
)
time.sleep(3) time.sleep(3)
else: else:
logger.error( logger.error(f"Error en todos los intentos de traducción de celdas desde {start_idx} a {end_idx}: {e}")
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}")
)
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.update(end_idx)
@ -209,9 +279,7 @@ def main(config: TranslationConfig):
logger.info(f"Número total de traducciones recibidas: {len(translations)}") logger.info(f"Número total de traducciones recibidas: {len(translations)}")
# Inicializar ProgressBar para la fase de actualización del DataFrame # Inicializar ProgressBar para la fase de actualización del DataFrame
update_progress = fc.ProgressBar( update_progress = fc.ProgressBar(len(df), prefix='Actualizando DataFrame:', suffix='Completado')
len(df), prefix="Actualizando DataFrame:", suffix="Completado"
)
# Actualizar el DataFrame con las traducciones y hacemos la Traduccion inversa # Actualizar el DataFrame con las traducciones y hacemos la Traduccion inversa
for index, row in df.iterrows(): for index, row in df.iterrows():
@ -221,106 +289,63 @@ def main(config: TranslationConfig):
try: try:
google_translation = google_translate( google_translation = google_translate(
translations[celda_clave], 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 df.at[index, check_translate_col] = google_translation
except Exception as e: except Exception as e:
logger.error( logger.error(f"Error en la traducción de Google para el texto '{celda_clave}': {e}")
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, check_translate_col] = "Error en la traducción"
df.at[index, affinity_col] = 0.0 df.at[index, affinity_col] = 0.0
update_progress.increment() update_progress.increment()
update_progress.finish() update_progress.finish()
# Inicializar ProgressBar para la fase de cálculo de afinidad
# Configurar el modelo a usar affinity_progress = fc.ProgressBar(num_texts, prefix='Calculando afinidad:', suffix='Completado')
modelo_llm = fc.LLM_MODELS["OpenAI"] # o el que se prefiera
api_key = openai_api_key() # solo necesario para OpenAI y Grok
# Afinidades # Afinidades
# Los textos ya vienen del proceso de traducción affinities = {}
texts_to_check = {} for start_idx in range(0, num_texts, batch_size):
for key, translated_text in translations.items(): end_idx = min(start_idx + batch_size, num_texts)
if pd.notna(translated_text) and str(translated_text).strip() != "": batch_texts = dict(list(texts_to_translate.items())[start_idx:end_idx])
texts_to_check[key] = translated_text logger.info(f"Afinidad: celdas desde {start_idx} a {end_idx}.")
# Calcular afinidades usando LLM retries = 2
affinities_dict = fc.calcular_afinidad_batch( for attempt in range(retries):
texts_to_check, config.codigo_tipo_PLC, modelo_llm, logger, api_key try:
) batch_affinities = affinity_batch_openai(config.codigo_tipo_PLC, batch_texts)
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...")
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}")
for key, value in batch_texts.items():
try:
score = calcular_afinidad(config.codigo_tipo_PLC, key, value)
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}")
# Asignar resultados al DataFrame affinity_progress.increment()
affinity_progress.finish()
# Actualizar el DataFrame con las Afinidades
for index, row in df.iterrows(): for index, row in df.iterrows():
key = str(row[source_col]) celda_clave = str(row[source_col])
if key in affinities_dict: if celda_clave in affinities:
df.at[index, affinity_col] = affinities_dict[key] df.at[index, affinity_col] = affinities[celda_clave]
output_path = config.get_auto_translate_path() 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}") logger.info(f"Archivo traducido guardado en: {output_path}")
print(f"\nArchivo traducido guardado en: {output_path}") print(f"\nArchivo traducido guardado en: {output_path}")
def run(config: TranslationConfig): def run(config: TranslationConfig):
global logger global logger
logger = fc.configurar_logger(config.work_dir) logger = fc.configurar_logger(config.work_dir)
@ -328,8 +353,6 @@ def run(config: TranslationConfig):
print(f"\rIniciando: {script_name}\r") print(f"\rIniciando: {script_name}\r")
main(config) main(config)
if __name__ == "__main__": if __name__ == "__main__":
import menu_pasos_traduccion import menu_pasos_traduccion
menu_pasos_traduccion.main() menu_pasos_traduccion.main()

View File

@ -18,7 +18,6 @@ def importar_traduccion_manual(config: TranslationConfig):
archivo_traduccion = config.get_translate_path() archivo_traduccion = config.get_translate_path()
master_col = config.codigo_idioma_seleccionado 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_maestro = fc.read_dataframe_with_cleanup_retries(archivo_maestro)
df_traduccion = fc.read_dataframe_with_cleanup_retries(archivo_traduccion) df_traduccion = fc.read_dataframe_with_cleanup_retries(archivo_traduccion)
@ -29,7 +28,7 @@ def importar_traduccion_manual(config: TranslationConfig):
for index, fila in df_traduccion.iterrows(): for index, fila in df_traduccion.iterrows():
clave = fila[df_maestro.columns[0]] clave = fila[df_maestro.columns[0]]
if clave in df_maestro[df_maestro.columns[0]].values: if clave in df_maestro[df_maestro.columns[0]].values:
valor_traducido = fila[propuesto_col] # Use propuesto column valor_traducido = fila[master_col]
valor_original = df_maestro.loc[ valor_original = df_maestro.loc[
df_maestro[df_maestro.columns[0]] == clave, master_col df_maestro[df_maestro.columns[0]] == clave, master_col
].values[0] ].values[0]
@ -39,7 +38,6 @@ def importar_traduccion_manual(config: TranslationConfig):
and valor_traducido != "" and valor_traducido != ""
and str(valor_original) != str(valor_traducido) and str(valor_original) != str(valor_traducido)
): ):
okToSave, Error = fc.verificar_celda_traducida( okToSave, Error = fc.verificar_celda_traducida(
config.codigo_tipo_PLC, clave, valor_traducido config.codigo_tipo_PLC, clave, valor_traducido
) )
@ -64,7 +62,7 @@ def importar_traduccion_manual(config: TranslationConfig):
f"Fila {index}, Columna {master_col}: No actualizado porque: {Error}" f"Fila {index}, Columna {master_col}: No actualizado porque: {Error}"
) )
fila_excel = index + 2 fila_excel = index + 2
columna_excel = df_traduccion.columns.get_loc(propuesto_col) + 1 columna_excel = df_traduccion.columns.get_loc(master_col) + 1
celdas_con_errores[(fila_excel, columna_excel)] = Error celdas_con_errores[(fila_excel, columna_excel)] = Error
fc.save_dataframe_with_retries(df_maestro, output_path=archivo_maestro) fc.save_dataframe_with_retries(df_maestro, output_path=archivo_maestro)

View File

@ -4,7 +4,6 @@ import os
from translation_config import TranslationConfig from translation_config import TranslationConfig
from openpyxl import load_workbook from openpyxl import load_workbook
from openpyxl.styles import Font from openpyxl.styles import Font
from openpyxl.styles import PatternFill, Alignment, Font
# Definir el logger a nivel de módulo # Definir el logger a nivel de módulo
logger = None logger = None
@ -17,7 +16,6 @@ def importar_traduccion(config: TranslationConfig):
return return
master_col = config.codigo_idioma_seleccionado master_col = config.codigo_idioma_seleccionado
master_propuesto_col = f"{master_col}_Propuesto"
translated_col = f"{config.codigo_idioma_seleccionado} Translated" translated_col = f"{config.codigo_idioma_seleccionado} Translated"
affinity_col = f"{config.codigo_idioma_seleccionado} Affinity" affinity_col = f"{config.codigo_idioma_seleccionado} Affinity"
@ -26,106 +24,47 @@ def importar_traduccion(config: TranslationConfig):
config.get_auto_translate_path() 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 = {} 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(): for index, fila in df_traduccion.iterrows():
clave = fila[config.codigo_columna_maestra] clave = fila[df_maestro.columns[0]]
mascara = df_maestro[config.codigo_columna_maestra] == clave if clave in df_maestro[df_maestro.columns[0]].values:
if mascara.any():
if ( if (
pd.notna(fila[affinity_col]) fila[affinity_col] >= config.nivel_afinidad_minimo
and fila[affinity_col] >= config.nivel_afinidad_minimo and pd.notnull(fila[translated_col])
and pd.notna(fila[translated_col]) and fila[translated_col] != ""
): ):
valor_traducido = fila[translated_col] valor_traducido = fila[translated_col]
indice_maestro = df_maestro.index[mascara].tolist()[0] valor_original = df_maestro.loc[
df_maestro[df_maestro.columns[0]] == clave, master_col
# Determinar la columna a actualizar según el caso ].values[0]
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): if str(valor_original) != str(valor_traducido):
df_maestro.loc[indice_maestro, columna_destino] = valor_traducido df_maestro.loc[
df_maestro[df_maestro.columns[0]] == clave, master_col
] = valor_traducido
logger.info( logger.info(
f'Fila {index}, Columna {columna_destino}: "{valor_original}" actualizado a "{valor_traducido}"' f'Fila {index}, Columna {translated_col}: "{valor_original}" actualizado a "{valor_traducido}"'
) )
fila_excel = indice_maestro + 2 fila_excel = (
columna_excel = df_maestro.columns.get_loc(columna_destino) + 1 df_maestro.index[
df_maestro[df_maestro.columns[0]] == clave
].tolist()[0]
+ 2
)
columna_excel = df_maestro.columns.get_loc(master_col) + 1
celdas_modificadas[(fila_excel, columna_excel)] = valor_traducido celdas_modificadas[(fila_excel, columna_excel)] = valor_traducido
else :
# Guardar con formato Excel logger.error(
with pd.ExcelWriter(archivo_maestro, engine="openpyxl") as writer: f'Clave {clave} no encontrada en master.'
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
) )
# Aplicar negrita y color a las celdas modificadas fc.save_dataframe_with_retries(df_maestro, output_path=archivo_maestro)
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
if celdas_modificadas: aplicar_negrita_celdas_modificadas(archivo_maestro, celdas_modificadas)
print(
f"Se han actualizado y marcado {len(celdas_modificadas)} celdas en el archivo maestro." print(f"Traducciones importadas y archivo maestro actualizado: {archivo_maestro}.")
)
else:
print("No se realizaron modificaciones en el archivo maestro.")
def aplicar_negrita_celdas_modificadas(archivo, celdas_modificadas): def aplicar_negrita_celdas_modificadas(archivo, celdas_modificadas):

View File

@ -16,139 +16,91 @@ def update_from_master(config: TranslationConfig, archivo_to_update):
print("El archivo maestro no existe.") print("El archivo maestro no existe.")
return return
logger.info(f"Iniciando actualización en {archivo_to_update} desde el archivo maestro. Para {config.codigo_idioma_seleccionado}") logger.info(" .... ")
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_maestro = fc.read_dataframe_with_cleanup_retries(archivo_maestro)
df_to_update = fc.read_dataframe_with_cleanup_retries(archivo_to_update) 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 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: celdas_con_errores = {}
print(f"Error: Columna {master_col} no encontrada en el archivo maestro") celdas_vacias = {}
return
celdas_modificadas = [] 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(): for index, fila in df_to_update.iterrows():
valor_original = fila[col_clave] valor_original = fila[col_clave]
clave = fc.compactar_celda_clave(config.codigo_tipo_PLC, valor_original) clave = fc.compactar_celda_clave(config.codigo_tipo_PLC, valor_original)
if not pd.isna(clave) and clave in df_maestro[col_clave].values: if not pd.isna(clave) and clave in df_maestro[col_clave].values:
indice_maestro = df_maestro.index[df_maestro[col_clave] == clave].tolist()[0] # logger.info(f"Fila {index} : Clave: {clave}")
valor_traducido_compacto = df_maestro.loc[indice_maestro, master_col]
indice_maestro = df_maestro.index[df_maestro[col_clave] == clave].tolist()[
0
]
valor_traducido_compacto = df_maestro.loc[
indice_maestro, config.codigo_idioma_seleccionado
]
if pd.isna(valor_traducido_compacto): if pd.isna(valor_traducido_compacto):
continue 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( valor_traducido = fc.decompactar_celda_traducida(
config.codigo_tipo_PLC, config.codigo_tipo_PLC,
celda_original=valor_original, celda_original=valor_original,
celda_traducida=valor_traducido_compacto celda_traducida=valor_traducido_compacto,
) )
if (
if not pd.isna(valor_traducido) and fila[config.codigo_idioma_seleccionado] != valor_traducido: not pd.isna(valor_traducido)
and fila[config.codigo_idioma_seleccionado] != valor_traducido
):
okToSave, Error = fc.verificar_celda_traducida( okToSave, Error = fc.verificar_celda_traducida(
config.codigo_tipo_PLC, clave, valor_traducido_compacto config.codigo_tipo_PLC, clave, valor_traducido_compacto
) )
if okToSave: 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}") 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
progress_bar.increment() progress_bar.increment()
progress_bar.finish() progress_bar.finish()
# Save updated file with formatting
nombre, extension = os.path.splitext(archivo_to_update) nombre, extension = os.path.splitext(archivo_to_update)
nuevo_nombre = f"{nombre}_import{extension}" 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: marcar_celdas_con_errores(
df_to_update.to_excel(writer, sheet_name="User Texts", index=False) archivo_maestro,
celdas_con_errores,
celdas_vacias,
config.codigo_idioma_seleccionado,
)
workbook = writer.book print(
worksheet = writer.sheets["User Texts"] f"Se han actualizado las filas en {archivo_to_update} desde el archivo maestro."
)
# Format columns print(
from openpyxl.utils import get_column_letter f"Se han marcado {len(celdas_con_errores)} celdas con errores en el archivo maestro."
from openpyxl.styles import Alignment, PatternFill )
print(f"Se han marcado {len(celdas_vacias)} celdas vacías en el archivo maestro.")
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
# 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)
workbook = writer.book
worksheet = writer.sheets["User Texts"]
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(" .... ") 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) workbook = load_workbook(archivo_maestro)
sheet = workbook.active sheet = workbook.active
@ -160,12 +112,8 @@ def marcar_celdas_con_errores(
print(f"No se encontró la columna para el idioma {target_lang_code}") print(f"No se encontró la columna para el idioma {target_lang_code}")
return return
error_fill = PatternFill( error_fill = PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid")
start_color="FF0000", end_color="FF0000", fill_type="solid" empty_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", 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 white_font = Font(color="FFFFFF") # Fuente blanca para celdas con fondo rojo
for indice_maestro, mensaje_error in celdas_con_errores.items(): for indice_maestro, mensaje_error in celdas_con_errores.items():
@ -184,9 +132,7 @@ def marcar_celdas_con_errores(
cell.comment = comment cell.comment = comment
workbook.save(archivo_maestro) workbook.save(archivo_maestro)
print( print(f"Se han marcado las celdas con errores y vacías en el archivo maestro {archivo_maestro}")
f"Se han marcado las celdas con errores y vacías en el archivo maestro {archivo_maestro}"
)
def run(config: TranslationConfig, archivo_to_update): def run(config: TranslationConfig, archivo_to_update):