Compare commits
No commits in common. "4e1dfbdbcda88829e6ffc4d8dc5616ccede41fab" and "b31644553ee032638f7e461d5d635b49911c0f60" have entirely different histories.
4e1dfbdbcd
...
b31644553e
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
This texts are for an HMI industrial machine. Preserve the next words without translation: TILTER, ON, OFF, HMI, STOP, SD, USB, PLC, PID, FF, VFD, +A, +B, +CG, +D, +E, UPS, EMD, Pack, TableTop, Air, DCS, SKID, ALLEN BRADLEY, CPU, DANFOSS, Vetromeccanica, mBar, m/sec, mm, EEPROM, Ethernet, FIFO, PDF, RAM.
|
|
@ -1 +0,0 @@
|
||||||
from .funciones_base import *
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,51 +0,0 @@
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
# 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"),
|
|
||||||
}
|
|
||||||
|
|
||||||
def mostrar_idiomas():
|
|
||||||
print("Selecciona el idioma de destino:")
|
|
||||||
for numero, (nombre, _) in IDIOMAS.items():
|
|
||||||
print(f"{numero}: {nombre}")
|
|
||||||
|
|
||||||
def transformar_texto(texto):
|
|
||||||
if pd.isnull(texto):
|
|
||||||
return texto
|
|
||||||
# Sustituir [[digits]] por <>
|
|
||||||
texto_transformado = re.sub(r'\[\[digits\]\]', '<>', texto)
|
|
||||||
# Sustituir cualquier <...> por <#>
|
|
||||||
texto_transformado = re.sub(r'<.*?>', '<#>', texto_transformado)
|
|
||||||
return texto_transformado
|
|
||||||
|
|
||||||
|
|
||||||
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, 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.")
|
|
|
@ -2,7 +2,6 @@ import pandas as pd
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from manejoArchivos import select_file
|
from manejoArchivos import select_file
|
||||||
import funciones_comunes
|
|
||||||
|
|
||||||
def es_columna_tipo_xxYY(columna):
|
def es_columna_tipo_xxYY(columna):
|
||||||
# Verificar si la columna es del tipo "xx-YY" usando una expresión regular
|
# Verificar si la columna es del tipo "xx-YY" usando una expresión regular
|
||||||
|
@ -23,7 +22,7 @@ def preprocesar_importacion(df_importacion):
|
||||||
# Sustituir en las demás columnas del tipo "xx-YY"
|
# Sustituir en las demás columnas del tipo "xx-YY"
|
||||||
for columna in df_importacion.columns:
|
for columna in df_importacion.columns:
|
||||||
if columna != 'it-IT' and es_columna_tipo_xxYY(columna):
|
if columna != 'it-IT' and es_columna_tipo_xxYY(columna):
|
||||||
df_importacion.at[index, columna] = funciones_comunes.transformar_texto(sustituir_digitos(fila[columna]))
|
df_importacion.at[index, columna] = sustituir_digitos(fila[columna])
|
||||||
|
|
||||||
# Guardar la clave sustituida
|
# Guardar la clave sustituida
|
||||||
df_importacion.at[index, 'it-IT'] = clave_sustituida
|
df_importacion.at[index, 'it-IT'] = clave_sustituida
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from manejoArchivos import select_file
|
from manejoArchivos import select_file
|
||||||
import funciones_comunes
|
|
||||||
|
def transformar_texto(texto):
|
||||||
|
# Sustituir [[digits]] por <>
|
||||||
|
texto_transformado = re.sub(r'\[\[digits\]\]', '<>', texto)
|
||||||
|
# Sustituir cualquier <...> por <#>
|
||||||
|
texto_transformado = re.sub(r'<.*?>', '<#>', texto_transformado)
|
||||||
|
return texto_transformado
|
||||||
|
|
||||||
def exportar_para_traduccion(archivo_maestro):
|
def exportar_para_traduccion(archivo_maestro):
|
||||||
if not os.path.exists(archivo_maestro):
|
if not os.path.exists(archivo_maestro):
|
||||||
|
@ -18,10 +25,10 @@ def exportar_para_traduccion(archivo_maestro):
|
||||||
|
|
||||||
# Transformar las demás columnas
|
# Transformar las demás columnas
|
||||||
for columna in df_maestro.columns[1:]:
|
for columna in df_maestro.columns[1:]:
|
||||||
df_export[columna] = df_maestro[columna].apply(lambda x: funciones_comunes.transformar_texto(str(x)) if pd.notnull(x) else x)
|
df_export[columna] = df_maestro[columna].apply(lambda x: transformar_texto(str(x)) if pd.notnull(x) else x)
|
||||||
|
|
||||||
# Guardar el archivo exportado
|
# Guardar el archivo exportado
|
||||||
ruta_export = os.path.join(os.path.dirname(archivo_maestro), '2_master_export2translate.xlsx')
|
ruta_export = os.path.join(os.path.dirname(archivo_maestro), '.\\data\\2_master_export2translate.xlsx')
|
||||||
df_export.to_excel(ruta_export, index=False)
|
df_export.to_excel(ruta_export, index=False)
|
||||||
print(f"Archivo exportado para traducción: {ruta_export}")
|
print(f"Archivo exportado para traducción: {ruta_export}")
|
||||||
|
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
import funciones_comunes
|
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
from manejoArchivos import select_file
|
|
||||||
|
|
||||||
def configurar_logger(ruta_log):
|
|
||||||
os.makedirs(".\\data", exist_ok=True)
|
|
||||||
logger = logging.getLogger('.\\data\\importacion_logger')
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
fh = logging.FileHandler(ruta_log, encoding='utf-8')
|
|
||||||
fh.setLevel(logging.INFO)
|
|
||||||
formatter = logging.Formatter('%(asctime)s - %(message)s')
|
|
||||||
fh.setFormatter(formatter)
|
|
||||||
logger.addHandler(fh)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
def revertir_transformaciones(texto, digitos, secciones):
|
|
||||||
# Revertir <> a [[digits]]
|
|
||||||
for digito in digitos:
|
|
||||||
texto = texto.replace('<>', digito, 1)
|
|
||||||
# Revertir <#> a <...> usando las secciones originales
|
|
||||||
for seccion in secciones:
|
|
||||||
texto = texto.replace('<#>', f'<{seccion}>', 1)
|
|
||||||
return texto
|
|
||||||
|
|
||||||
def importar_traduccion(archivo_maestro, archivo_traduccion, target_lang_code, nivel_afinidad_minimo):
|
|
||||||
if not os.path.exists(archivo_maestro):
|
|
||||||
print("El archivo maestro no existe.")
|
|
||||||
return
|
|
||||||
|
|
||||||
master_col = target_lang_code
|
|
||||||
translated_col = f"{target_lang_code} Translated"
|
|
||||||
affinity_col = f"{target_lang_code} Affinity"
|
|
||||||
|
|
||||||
df_maestro = pd.read_excel(archivo_maestro)
|
|
||||||
df_traduccion = pd.read_excel(archivo_traduccion)
|
|
||||||
|
|
||||||
# Configurar el logger
|
|
||||||
directorio = os.path.dirname(archivo_maestro)
|
|
||||||
nombre_log = os.path.join(directorio, 'importacion_traduccion.log')
|
|
||||||
logger = configurar_logger(nombre_log)
|
|
||||||
|
|
||||||
# Iterar sobre las filas del archivo de traducción para actualizar el maestro
|
|
||||||
for index, fila in df_traduccion.iterrows():
|
|
||||||
clave = fila[df_maestro.columns[0]]
|
|
||||||
if clave in df_maestro[df_maestro.columns[0]].values:
|
|
||||||
# Comprobar afinidad y valores no nulos/vacíos
|
|
||||||
if fila[affinity_col] >= nivel_afinidad_minimo and pd.notnull(fila[translated_col]) and fila[translated_col] != "":
|
|
||||||
valor_traducido = fila[translated_col]
|
|
||||||
valor_original = df_maestro.loc[df_maestro[df_maestro.columns[0]] == clave, master_col].values[0]
|
|
||||||
|
|
||||||
if str(valor_original) != str(valor_traducido):
|
|
||||||
df_maestro.loc[df_maestro[df_maestro.columns[0]] == clave, master_col] = valor_traducido
|
|
||||||
logger.info(f'Fila {index}, Columna {translated_col}: "{valor_original}" actualizado a "{valor_traducido}"')
|
|
||||||
|
|
||||||
# Guardar el archivo maestro actualizado
|
|
||||||
funciones_comunes.save_dataframe_with_retries(df_maestro,output_path=archivo_maestro)
|
|
||||||
print(f"Traducciones importadas y archivo maestro actualizado: {archivo_maestro}. Detalles de los cambios en {nombre_log}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
archivo_maestro = ".\\data\\1_hmi_master_translates.xlsx"
|
|
||||||
archivo_traduccion = ".\\data\\3_master_export2translate_translated.xlsx"
|
|
||||||
|
|
||||||
nivel_afinidad_minimo = input("Introduce el nivel minimo de afinidad para importar (presiona Enter para usar el valor por defecto 0.5): ")
|
|
||||||
nivel_afinidad_minimo = float(nivel_afinidad_minimo) if nivel_afinidad_minimo else 0.5
|
|
||||||
|
|
||||||
funciones_comunes.mostrar_idiomas()
|
|
||||||
seleccion_idioma = int(input("Introduce el número del idioma de destino: "))
|
|
||||||
if seleccion_idioma not in funciones_comunes.IDIOMAS:
|
|
||||||
print("Selección inválida.")
|
|
||||||
else:
|
|
||||||
target_lang, target_lang_code = funciones_comunes.IDIOMAS[seleccion_idioma]
|
|
||||||
importar_traduccion(archivo_maestro, archivo_traduccion, target_lang_code, nivel_afinidad_minimo )
|
|
|
@ -5,17 +5,50 @@ import re
|
||||||
import logging
|
import logging
|
||||||
from openai_api_key import openai_api_key
|
from openai_api_key import openai_api_key
|
||||||
from google_api_key import google_api_key
|
from google_api_key import google_api_key
|
||||||
|
from x2_master_export2translate import transformar_texto
|
||||||
import ollama
|
import ollama
|
||||||
import json
|
import json
|
||||||
from google.cloud import translate_v2 as translate
|
from google.cloud import translate_v2 as translate
|
||||||
from google.oauth2 import service_account
|
from google.oauth2 import service_account
|
||||||
import html
|
import html
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
import funciones_comunes
|
import time
|
||||||
|
|
||||||
openai_client = OpenAI(api_key=openai_api_key())
|
openai_client = OpenAI(api_key=openai_api_key())
|
||||||
GOOGLE_APPLICATION_CREDENTIALS = "translate-431108-020c17463fbb.json"
|
GOOGLE_APPLICATION_CREDENTIALS = "translate-431108-020c17463fbb.json"
|
||||||
|
|
||||||
|
# Diccionario de idiomas
|
||||||
|
IDIOMAS = {
|
||||||
|
1: ("English", "en-GB"),
|
||||||
|
2: ("Portuguese", "pt-PT"),
|
||||||
|
3: ("Spanish", "es-ES"),
|
||||||
|
4: ("Russian", "ru-RU"),
|
||||||
|
5: ("French", "fr-FR"),
|
||||||
|
6: ("German", "de-DE"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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, index=False)
|
||||||
|
print("Archivo guardado exitosamente.")
|
||||||
|
return
|
||||||
|
except PermissionError as e:
|
||||||
|
print(f"Error de permiso: {e}. 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 configurar_logger():
|
def configurar_logger():
|
||||||
logger = logging.getLogger("translate_logger")
|
logger = logging.getLogger("translate_logger")
|
||||||
|
@ -54,6 +87,12 @@ def google_translate(text, target_language):
|
||||||
logger = configurar_logger()
|
logger = configurar_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def mostrar_idiomas():
|
||||||
|
print("Selecciona el idioma de destino:")
|
||||||
|
for numero, (nombre, _) in IDIOMAS.items():
|
||||||
|
print(f"{numero}: {nombre}")
|
||||||
|
|
||||||
|
|
||||||
def read_system_prompt():
|
def read_system_prompt():
|
||||||
try:
|
try:
|
||||||
with open(".\\data\\system_prompt.txt", "r", encoding="utf-8") as file:
|
with open(".\\data\\system_prompt.txt", "r", encoding="utf-8") as file:
|
||||||
|
@ -130,7 +169,7 @@ def affinity_batch_openai(texts_dict):
|
||||||
"Evaluate the semantic similarity between the following table of pairs of texts in json format on a scale from 0 to 1. "
|
"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."
|
"Return the similarity scores for every row in JSON format as a list of numbers, without any additional text or formatting."
|
||||||
)
|
)
|
||||||
original_list = [funciones_comunes.transformar_texto(key) for key in texts_dict.keys()]
|
original_list = [transformar_texto(key) for key in texts_dict.keys()]
|
||||||
re_translated_list = list(texts_dict.values())
|
re_translated_list = list(texts_dict.values())
|
||||||
|
|
||||||
request_payload = json.dumps(
|
request_payload = json.dumps(
|
||||||
|
@ -204,7 +243,7 @@ def main(file_path, target_lang_code, target_lang, traducir_todo, batch_size=10)
|
||||||
if source_translated_col in df.columns
|
if source_translated_col in df.columns
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
processed_text = funciones_comunes.transformar_texto(source_text)
|
processed_text = transformar_texto(source_text)
|
||||||
|
|
||||||
if traducir_todo:
|
if traducir_todo:
|
||||||
if texto_requiere_traduccion(processed_text):
|
if texto_requiere_traduccion(processed_text):
|
||||||
|
@ -314,7 +353,7 @@ def main(file_path, target_lang_code, target_lang, traducir_todo, batch_size=10)
|
||||||
output_path = os.path.join(
|
output_path = os.path.join(
|
||||||
os.path.dirname(file_path), "3_master_export2translate_translated.xlsx"
|
os.path.dirname(file_path), "3_master_export2translate_translated.xlsx"
|
||||||
)
|
)
|
||||||
funciones_comunes.save_dataframe_with_retries(df,output_path=output_path)
|
save_dataframe_with_retries(df,output_path=output_path)
|
||||||
logger.info(f"Archivo traducido guardado en: {output_path}")
|
logger.info(f"Archivo traducido guardado en: {output_path}")
|
||||||
print(f"Archivo traducido guardado en: {output_path}")
|
print(f"Archivo traducido guardado en: {output_path}")
|
||||||
|
|
||||||
|
@ -323,12 +362,12 @@ if __name__ == "__main__":
|
||||||
batch_size = 20
|
batch_size = 20
|
||||||
translate_file = ".\\data\\2_master_export2translate.xlsx"
|
translate_file = ".\\data\\2_master_export2translate.xlsx"
|
||||||
|
|
||||||
funciones_comunes.mostrar_idiomas()
|
mostrar_idiomas()
|
||||||
seleccion_idioma = int(input("Introduce el número del idioma de destino: "))
|
seleccion_idioma = int(input("Introduce el número del idioma de destino: "))
|
||||||
if seleccion_idioma not in funciones_comunes.IDIOMAS:
|
if seleccion_idioma not in IDIOMAS:
|
||||||
print("Selección inválida.")
|
print("Selección inválida.")
|
||||||
else:
|
else:
|
||||||
target_lang, target_lang_code = funciones_comunes.IDIOMAS[seleccion_idioma]
|
target_lang, target_lang_code = IDIOMAS[seleccion_idioma]
|
||||||
traducir_todo = (
|
traducir_todo = (
|
||||||
input("¿Desea traducir todas las celdas (s/n)? ").strip().lower() == "s"
|
input("¿Desea traducir todas las celdas (s/n)? ").strip().lower() == "s"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
import funciones_comunes
|
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
from manejoArchivos import select_file
|
|
||||||
|
|
||||||
|
|
||||||
def configurar_logger(ruta_log):
|
|
||||||
os.makedirs(".\\data", exist_ok=True)
|
|
||||||
logger = logging.getLogger(".\\data\\importacion_logger")
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
fh = logging.FileHandler(ruta_log, encoding="utf-8")
|
|
||||||
fh.setLevel(logging.INFO)
|
|
||||||
formatter = logging.Formatter("%(asctime)s - %(message)s")
|
|
||||||
fh.setFormatter(formatter)
|
|
||||||
logger.addHandler(fh)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
def revertir_transformaciones(texto, digitos, secciones):
|
|
||||||
# Revertir <> a [[digits]]
|
|
||||||
for digito in digitos:
|
|
||||||
texto = texto.replace("<>", digito, 1)
|
|
||||||
# Revertir <#> a <...> usando las secciones originales
|
|
||||||
for seccion in secciones:
|
|
||||||
texto = texto.replace("<#>", f"<{seccion}>", 1)
|
|
||||||
return texto
|
|
||||||
|
|
||||||
|
|
||||||
def complete_emptys(archivo_maestro, target_lang_code, second_lang_code):
|
|
||||||
if not os.path.exists(archivo_maestro):
|
|
||||||
print("El archivo maestro no existe.")
|
|
||||||
return
|
|
||||||
|
|
||||||
master_col = target_lang_code
|
|
||||||
second_col = second_lang_code
|
|
||||||
|
|
||||||
df_maestro = pd.read_excel(archivo_maestro)
|
|
||||||
|
|
||||||
# Configurar el logger
|
|
||||||
directorio = os.path.dirname(archivo_maestro)
|
|
||||||
nombre_log = os.path.join(directorio, "importacion_traduccion.log")
|
|
||||||
logger = configurar_logger(nombre_log)
|
|
||||||
|
|
||||||
# Iterar sobre las filas del archivo de traducción para actualizar el maestro
|
|
||||||
for index, fila in df_maestro.iterrows():
|
|
||||||
clave = fila[df_maestro.columns[0]]
|
|
||||||
if fila[master_col] == "" or pd.isnull(fila[master_col]):
|
|
||||||
if pd.notnull(fila[second_col]) and fila[second_col] != "":
|
|
||||||
df_maestro.loc[
|
|
||||||
df_maestro[df_maestro.columns[0]] == clave, master_col
|
|
||||||
] = fila[second_col]
|
|
||||||
logger.info(
|
|
||||||
f'Fila {index}, Columna {master_col}: " actualizado a "{fila[second_col]}"'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
df_maestro.loc[
|
|
||||||
df_maestro[df_maestro.columns[0]] == clave, master_col
|
|
||||||
] = fila[df_maestro.columns[0]]
|
|
||||||
logger.info(
|
|
||||||
f'Fila {index}, Columna {master_col}: " actualizado a "{fila[df_maestro.columns[0]]}"'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Guardar el archivo maestro actualizado
|
|
||||||
funciones_comunes.save_dataframe_with_retries(
|
|
||||||
df_maestro, output_path=archivo_maestro
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
f"Traducciones importadas y archivo maestro actualizado: {archivo_maestro}. Detalles de los cambios en {nombre_log}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
archivo_maestro = ".\\data\\1_hmi_master_translates.xlsx"
|
|
||||||
|
|
||||||
funciones_comunes.mostrar_idiomas()
|
|
||||||
seleccion_idioma = int(input("Introduce el número del idioma de destino: "))
|
|
||||||
if seleccion_idioma not in funciones_comunes.IDIOMAS:
|
|
||||||
print("Selección inválida.")
|
|
||||||
exit
|
|
||||||
seleccion_idioma_secundario = int(
|
|
||||||
input(
|
|
||||||
"Introduce el número del idioma de secundario para copiar desde en caso de vacios: "
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if seleccion_idioma_secundario not in funciones_comunes.IDIOMAS:
|
|
||||||
print("Selección inválida.")
|
|
||||||
exit
|
|
||||||
|
|
||||||
_, target_lang_code = funciones_comunes.IDIOMAS[seleccion_idioma]
|
|
||||||
_, second_lang_code = funciones_comunes.IDIOMAS[seleccion_idioma_secundario]
|
|
||||||
complete_emptys(archivo_maestro, target_lang_code, second_lang_code)
|
|
Loading…
Reference in New Issue