import re import time import pandas as pd from openpyxl import load_workbook import logging import os from openpyxl.utils.escape import unescape # Diccionario de idiomas IDIOMAS = { 0: ("Italian", "it-IT"), 1: ("English", "en-GB"), 2: ("Portuguese", "pt-PT"), 3: ("Spanish", "es-ES"), 4: ("Russian", "ru-RU"), 5: ("French", "fr-FR"), 6: ("German", "de-DE"), 7: ("English_US", "en-US"), } # Función para obtener el shortcode a partir del código completo def idiomas_shortcodefromcode(code): if code: return code.split('-')[0] return None # O puedes lanzar una excepción si prefieres # Función para obtener el idioma a partir del código def idiomas_idiomafromcode(code): for idx, (idioma, codigo) in IDIOMAS.items(): if codigo == code: return idioma return None # O puedes lanzar una excepción si prefieres # Función para obtener el código a partir del idioma def idiomas_codefromidioma(idioma): for idx, (nombre_idioma, codigo) in IDIOMAS.items(): if nombre_idioma == idioma: return codigo return None # O lanzar una excepción # Función para verificar si un código existe def idiomas_existecode(code): return any(codigo == code for idx, (idioma, codigo) in IDIOMAS.items()) # Función para verificar si un idioma existe def idiomas_existeidioma(idioma): return any(nombre_idioma == idioma for idx, (nombre_idioma, codigo) in IDIOMAS.items()) # Función para obtener el idioma a partir del índice def idiomas_idiomafromindex(index): if index in IDIOMAS: return IDIOMAS[index][0] else: return None # O lanzar una excepción # Función para obtener el código a partir del índice def idiomas_codefromindex(index): if index in IDIOMAS: return IDIOMAS[index][1] else: return None # O lanzar una excepción # Función para obtener el índice a partir del código def idiomas_indexfromcode(code): for idx, (idioma, codigo) in IDIOMAS.items(): if codigo == code: return idx return None # O lanzar una excepción # Función para obtener el índice a partir del idioma def idiomas_indexfromidioma(idioma): for idx, (nombre_idioma, codigo) in IDIOMAS.items(): if nombre_idioma == idioma: return idx return None # O lanzar una excepción def mostrar_idiomas(): print("Selecciona el idioma de destino:") for numero, (nombre, _) in IDIOMAS.items(): print(f"{numero}: {nombre}") def configurar_logger(): logger = logging.getLogger("translate_logger") # Si el logger ya tiene handlers, asumimos que ya está configurado y lo devolvemos if logger.handlers: return logger logger.setLevel(logging.DEBUG) os.makedirs(".\\logs", exist_ok=True) fh = logging.FileHandler(".\\logs\\translate_log.log", encoding="utf-8") fh.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s : ") fh.setFormatter(formatter) logger.addHandler(fh) return logger def limpiar_caracteres_especiales(texto): if isinstance(texto, str): return unescape(texto) return texto def read_excel_with_cleanup(file_path, **kwargs): df = pd.read_excel(file_path, **kwargs) for col in df.columns: df[col] = df[col].apply(lambda x: limpiar_caracteres_especiales(x) if pd.notna(x) else x) return df def read_dataframe_with_cleanup_retries(file_path, max_retries=5, retry_delay=5, **kwargs): """ Lee un archivo Excel con limpieza de caracteres especiales, reintentando si el archivo está en uso. :param file_path: La ruta del archivo Excel a leer. :param max_retries: El número máximo de reintentos en caso de error. :param retry_delay: El tiempo de espera (en segundos) entre cada reintento. :param kwargs: Argumentos adicionales para pd.read_excel. :return: DataFrame con los datos limpios. """ retries = 0 while retries < max_retries: try: df = pd.read_excel(file_path, **kwargs) # Aplicar limpieza de caracteres especiales for col in df.columns: df[col] = df[col].apply(lambda x: limpiar_caracteres_especiales(x) if pd.notna(x) else x) print(f"Archivo leído y limpiado exitosamente: {file_path}") return df except PermissionError as e: print(f"Error de permiso: {e}. Por favor cierre el archivo. Reintentando en {retry_delay} segundos...") retries += 1 time.sleep(retry_delay) except Exception as e: print(f"Error inesperado: {e}. Reintentando en {retry_delay} segundos...") retries += 1 time.sleep(retry_delay) raise Exception(f"No se pudo leer el archivo después de {max_retries} intentos.") # # Salvar archivo Excel controlando que no este abierto. Sino espera. # def save_dataframe_with_retries(df, output_path, max_retries=5, retry_delay=5): """ Guarda un DataFrame en un archivo Excel, reintentando si el archivo está en uso. :param df: El DataFrame a guardar. :param output_path: La ruta del archivo donde se guardará el DataFrame. :param max_retries: El número máximo de reintentos en caso de error. :param retry_delay: El tiempo de espera (en segundos) entre cada reintento. """ retries = 0 while retries < max_retries: try: df.to_excel(output_path, sheet_name="User Texts", index=False) print("Archivo guardado exitosamente.") return except PermissionError as e: print( f"Error de permiso: {e}. Por favor cierre el archivo. Reintentando en {retry_delay} segundos..." ) retries += 1 time.sleep(retry_delay) print(f"No se pudo guardar el archivo después de {max_retries} intentos.") def cambiar_nombre_hoja(archivo_excel, nombre_hoja_actual, nombre_hoja_nuevo): # Cargar el archivo Excel existente libro = load_workbook(archivo_excel) # Verificar si la hoja existe en el archivo if nombre_hoja_actual in libro.sheetnames: # Obtener la hoja actual hoja = libro[nombre_hoja_actual] # Cambiar el nombre de la hoja hoja.title = nombre_hoja_nuevo # Guardar los cambios en el archivo Excel libro.save(archivo_excel) print( f"El nombre de la hoja ha sido cambiado de '{nombre_hoja_actual}' a '{nombre_hoja_nuevo}'." ) else: print(f"La hoja '{nombre_hoja_actual}' no existe en el archivo.") # Verificar si la columna es del tipo "xx-YY" usando una expresión regular def es_columna_tipo_xxYY(columna): # Verificar si la columna es del tipo "xx-YY" usando una expresión regular return bool(re.match(r"^[a-z]{2}-[A-Z]{2}$", columna)) def compactar_celda_clave(tipo_PLC, celda_original): if tipo_PLC == "siemens" : return compactar_celda_clave_siemens(celda_original) else : return compactar_celda_clave_ab(celda_original) def compactar_celda_traducida(tipo_PLC, celda_traducida): if tipo_PLC == "siemens" : return compactar_celda_traducida_siemens(celda_traducida) else : return compactar_celda_traducida_ab(celda_traducida) def obtener_digitos_celda_original(tipo_PLC, celda_original): if tipo_PLC == "siemens" : return obtener_digitos_celda_original_siemens(celda_original) else : return obtener_digitos_celda_original_ab(celda_original) def decompactar_celda_traducida(tipo_PLC, celda_original, celda_traducida): if tipo_PLC == "siemens" : return decompactar_celda_traducida_siemens(celda_original, celda_traducida) else : return decompactar_celda_traducida_ab(celda_original, celda_traducida) def verificar_celda_traducida(tipo_PLC, celda_clave, celda_traducida): if tipo_PLC == "siemens" : return verificar_celda_traducida_siemens(celda_clave, celda_traducida) else : return verificar_celda_traducida_ab(celda_clave, celda_traducida) def texto_requiere_traduccion(tipo_PLC, texto, logger): if tipo_PLC == "siemens" : return texto_requiere_traduccion_siemens(texto, logger) else : return texto_requiere_traduccion_ab(texto, logger) def texto_con_campos_especiales(tipo_PLC, texto): if tipo_PLC == "siemens" : return texto_con_campos_especiales_siemens(texto) else : return texto_con_campos_especiales_ab(texto) # SIEMENS # # Transforma: "A271/47/6 Air - M - Necessaria Manutenzione Filtro" -> # "A[[digits]]/[[digits]]/[[digits]] Air - M - Necessaria Manutenzione Filtro" # # Este procesamiento se aplica a las celdas clave def compactar_celda_clave_siemens(celda_original): if pd.isnull(celda_original): return celda_original def reemplazar(match): if match.group(1): # Si hay contenido dentro de <> return f"<{match.group(1)}>" return "[[digits]]" # Reemplaza dígitos fuera de <> con [[digits]], y preserva el contenido dentro de <> return re.sub(r"<(.*?)>|\d+", reemplazar, str(celda_original)) # SIEMENS def texto_requiere_traduccion_siemens(texto, logger): palabras = re.findall(r"\b\w{4,}\b", texto) campos_especiales = re.findall(r"<.*?>", texto) requiere_traduccion = len(palabras) > 0 or len(campos_especiales) != len( re.findall(r"<#>", texto) ) logger.debug( f"Decisión de traducción para texto '{texto}': {'Sí' if requiere_traduccion else 'No'} (palabras > 3 letras: {len(palabras) > 0}, solo campos especiales: {len(campos_especiales) == len(re.findall(r'<#>', texto))})" ) return requiere_traduccion # SIEMENS def texto_con_campos_especiales_siemens(texto): campos_especiales = len(re.findall(r"<#>", texto) ) return campos_especiales > 0 # SIEMENS # # Transforma: "A[[digits]]/[[digits]]/[[digits]] Air - M - Necessaria Manutenzione Filtro" -> # "A<>/<>/<> Air - M<#>" /> - Necessaria Manutenzione Filtro" # # Este procesamiento se aplica a las celdas traducidas def compactar_celda_traducida_siemens(celda_traducida): if pd.isnull(celda_traducida): return celda_traducida celda_traducida = compactar_celda_clave_siemens(celda_traducida) def reemplazar(match): if match.group(1): # Si hay contenido dentro de <> return "<#>" return "<>" # Reemplaza <...> con <#> y [[digits]] con <> return re.sub(r"<(.*?)>|\[\[digits\]\]", reemplazar, str(celda_traducida)) # SIEMENS # de "A271/47/6 Air - M - Necessaria Manutenzione Filtro" -> [271,47,6] # Obtener la secuencias de dígitos por [[digits]] def obtener_digitos_celda_original_siemens(celda_original): if pd.isnull(celda_original): return [] # Primero, reemplazamos temporalmente el contenido de los tags con un marcador texto_sin_tags = re.sub(r'<[^>]*>', '<>', str(celda_original)) # Ahora buscamos los dígitos digitos = re.findall(r'\d+', texto_sin_tags) return digitos # SIEMENS # Original Traducida # Transforma: "A271/47/6 Air - M - Necessaria Manutenzione Filtro" , "A<>/<>/<> Air - M<#> - Filter Maintenance Required" -> # "A271/47/6 Air - M - Necessaria Manutenzione Filtro" # # Este procesamiento se aplica a las celdas traducidas para regresar al valor original def decompactar_celda_traducida_siemens(celda_original, celda_traducida): digitos = obtener_digitos_celda_original_siemens(celda_original) celda_destino = celda_traducida # Replace <> with digits for d in digitos: celda_destino = celda_destino.replace("<>", d, 1) # Replace <#> with original content within <> original_tags = re.findall(r"<.*?>", celda_original) translated_tags = re.findall(r"<#>", celda_destino) for orig, trans in zip(original_tags, translated_tags): celda_destino = celda_destino.replace(trans, orig, 1) return celda_destino # # SIEMENS # def verificar_celda_traducida_siemens(celda_clave, celda_traducida): # Contar los placeholders de dígitos digitos_clave = celda_clave.count("[[digits]]") digitos_traducida = celda_traducida.count("<>") # Contar los placeholders de tags tags_clave = sum(1 for tag in re.findall(r"<.*?>", celda_clave) if tag != "[[digits]]") tags_traducida = celda_traducida.count("<#>") # Verificar si las cantidades coinciden if digitos_clave == digitos_traducida and tags_clave == tags_traducida: return True , "" else: text_error = f"Error de verificación:" + f" - Celda clave: {celda_clave}" + f" - Celda traducida: {celda_traducida}" text_error += f" - Dígitos en clave: {digitos_clave}, Dígitos en traducida: {digitos_traducida}" text_error += f" - Tags en clave: {tags_clave}, Tags en traducida: {tags_traducida}" return False, text_error # ALLEN BRADLEY def texto_requiere_traduccion_ab(texto, logger): palabras = re.findall(r"\b\w{4,}\b", texto) campos_especiales = re.findall(r"/\*.*?\*/", texto) total_palabras_largas = len(palabras) > 0 total_campos_especiales = len(campos_especiales) total_campos_marcados = len(re.findall(r"/\*#\*/", texto)) solo_campos_especiales = total_campos_especiales == total_campos_marcados requiere_traduccion = total_palabras_largas or not solo_campos_especiales logger.debug( f"Decisión de traducción para texto '{texto}': {'Sí' if requiere_traduccion else 'No'} " f"(palabras > 3 letras: {total_palabras_largas}, " f"solo campos especiales: {solo_campos_especiales})" ) return requiere_traduccion # ALLEN BRADLEY # Transforma: "A271/47/6 Air - M/*field ref="0" */ - Necessaria Manutenzione Filtro" -> # "A[[digits]]/[[digits]]/[[digits]] Air - M/*field ref="[[digits]]" */ - Necessaria Manutenzione Filtro" # # Este procesamiento se aplica a las celdas clave def compactar_celda_clave_ab(celda_original): if pd.isnull(celda_original): return celda_original def reemplazar(match): if match.group(1): # Si hay contenido dentro de /*...*/ return f"/*{match.group(1)}*/" return "[[digits]]" # Reemplaza dígitos fuera de /*...*/ con [[digits]], y preserva el contenido dentro de /*...*/ return re.sub(r"/\*(.*?)\*/|\d+", reemplazar, str(celda_original)) # ALLEN BRADLEY # Transforma: "A[[digits]]/[[digits]]/[[digits]] Air - M/*field ref="[[digits]]" */ - Necessaria Manutenzione Filtro" -> # "A<>/<>/<> Air - M/*#*/ - Necessaria Manutenzione Filtro" # # Este procesamiento se aplica a las celdas traducidas def compactar_celda_traducida_ab(celda_traducida): if pd.isnull(celda_traducida): return celda_traducida celda_traducida = compactar_celda_clave_ab(celda_traducida) def reemplazar(match): if match.group(1): # Si hay contenido dentro de /*...*/ return "/*#*/" return "<>" # Reemplaza /*...*/ con /*#*/ y [[digits]] con <> return re.sub(r"/\*(.*?)\*/|\[\[digits\]\]", reemplazar, str(celda_traducida)) # ALLEN BRADLEY # De "A271/47/6 Air - M/*field ref="0" */ - Necessaria Manutenzione Filtro" -> [271,47,6] # Obtener las secuencias de dígitos para [[digits]] def obtener_digitos_celda_original_ab(celda_original): if pd.isnull(celda_original): return [] # Reemplazamos temporalmente el contenido de los comentarios con un marcador texto_sin_tags = re.sub(r'/\*[^*]*\*/', '<>', str(celda_original)) # Ahora buscamos los dígitos digitos = re.findall(r'\d+', texto_sin_tags) return digitos # ALLEN BRADLEY # Transformación para regresar al valor original def decompactar_celda_traducida_ab(celda_original, celda_traducida): digitos = obtener_digitos_celda_original_ab(celda_original) celda_destino = celda_traducida # Reemplaza <> con dígitos for d in digitos: celda_destino = celda_destino.replace("<>", d, 1) # Reemplaza /*#*/ con el contenido original dentro de /*...*/ original_tags = re.findall(r"/\*.*?\*/", celda_original) translated_tags = re.findall(r"/\*#\*/", celda_destino) for orig, trans in zip(original_tags, translated_tags): celda_destino = celda_destino.replace(trans, orig, 1) return celda_destino # # ALLEN BRADLEY # def verificar_celda_traducida_ab(celda_clave, celda_traducida): # Contar los placeholders de dígitos digitos_clave = celda_clave.count("[[digits]]") digitos_traducida = celda_traducida.count("<>") # Contar los placeholders de comentarios tags_clave = sum(1 for tag in re.findall(r"/\*.*?\*/", celda_clave) if tag != "[[digits]]") tags_traducida = celda_traducida.count("/*#*/") # Verificar si las cantidades coinciden if digitos_clave == digitos_traducida and tags_clave == tags_traducida: return True, "" else: text_error = f"Error de verificación:" + f" - Celda clave: {celda_clave}" + f" - Celda traducida: {celda_traducida}" text_error += f" - Dígitos en clave: {digitos_clave}, Dígitos en traducida: {digitos_traducida}" text_error += f" - Comentarios en clave: {tags_clave}, Comentarios en traducida: {tags_traducida}" return False, text_error # # ALLEN BRADLEY # def texto_con_campos_especiales_ab(texto): campos_especiales = len(re.findall(r"/\*#\*/", texto)) return campos_especiales > 0 if __name__ == "__main__": # SIEMENS print("****************** SIEMENS ***************************") celda_original = 'A271/47/6 Air - M - Necessaria Manutenzione Filtro' celda_original = 'DB/DB/DB' celda_original = 'Text' celda_clave = compactar_celda_clave_siemens(celda_original) celda_tradc = compactar_celda_traducida_siemens(celda_original) + " TEXTO " print() print("Celda Original : " +celda_original) print("Celda Clave : " + celda_clave) print("Celda Traducida: " + celda_tradc) print("Digitos : " + ','.join(obtener_digitos_celda_original_siemens(celda_original))) print("Celda : " + decompactar_celda_traducida_siemens(celda_original, celda_tradc)) print("Celda Original : " + celda_original) print(verificar_celda_traducida_siemens(celda_clave=celda_clave, celda_traducida= celda_tradc)) # ALLEN BRADLEY print("****************** ALLEN BRADLEY ***************************") celda_original = 'A271/47/6 /*N:4 {#1.#4.VFix[1]} NOFILL DP:1*/ m/min SPEED' celda_original = 'Formats_x000D_Comparing' celda_clave = compactar_celda_clave_ab(celda_original) celda_tradc = compactar_celda_traducida_ab(celda_original) + " TEXTO " celda_tradc = 'Formatos_x<>D_Comparación' print() print("Celda Original : " +celda_original) print("Celda Clave : " + celda_clave) print("Celda Traducida: " + celda_tradc) print("Digitos : " + ','.join(obtener_digitos_celda_original_ab(celda_original))) print("Celda : " + decompactar_celda_traducida_ab(celda_original, celda_tradc)) print("Celda Original : " + celda_original) print(verificar_celda_traducida_ab(celda_clave=celda_clave, celda_traducida= celda_tradc))