From 46477a713370a20ed7eee986ac32d5fe173531c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iker=20Garc=C3=ADa=20Calvi=C3=B1o?= Date: Tue, 15 Jul 2025 12:51:26 +0200 Subject: [PATCH] Changes to migrate to BCP 47 locale standard --- src/dashboard.py | 4 +- src/static/locale/active_languages.json | 112 --- src/static/locale/verify_locale_files.py | 62 -- .../{locale/ar-sa.json => locales/ar-SA.json} | 0 .../{locale/be.json => locales/be-BY.json} | 0 .../{locale/ca.json => locales/ca-ES.json} | 0 .../{locale/cs.json => locales/cs-CZ.json} | 0 .../{locale/de-de.json => locales/de-DE.json} | 0 .../{locale/es-es.json => locales/es-ES.json} | 651 ++++++++++-------- .../{locale/fa.json => locales/fa-IR.json} | 0 .../{locale/fr-ca.json => locales/fr-CA.json} | 0 .../{locale/fr-fr.json => locales/fr-FR.json} | 0 .../{locale/it-it.json => locales/it-IT.json} | 0 .../{locale/ja-jp.json => locales/ja-JP.json} | 0 .../{locale/ko.json => locales/ko-KR.json} | 0 src/static/locales/locale_manager.py | 301 ++++++++ .../locale_template.json} | 0 .../{locale/nl-nl.json => locales/nl-NL.json} | 0 .../{locale/pl.json => locales/pl-PL.json} | 0 .../{locale/ru.json => locales/ru-RU.json} | 0 src/static/locales/supported_locales.json | 134 ++++ .../{locale/sv-se.json => locales/sv-SE.json} | 0 .../{locale/th.json => locales/th-TH.json} | 0 .../{locale/tr-tr.json => locales/tr-TR.json} | 0 .../{locale/uk.json => locales/uk-UA.json} | 0 .../{locale/zh-cn.json => locales/zh-CN.json} | 0 .../{locale/zh-hk.json => locales/zh-HK.json} | 0 27 files changed, 791 insertions(+), 473 deletions(-) delete mode 100644 src/static/locale/active_languages.json delete mode 100644 src/static/locale/verify_locale_files.py rename src/static/{locale/ar-sa.json => locales/ar-SA.json} (100%) rename src/static/{locale/be.json => locales/be-BY.json} (100%) rename src/static/{locale/ca.json => locales/ca-ES.json} (100%) rename src/static/{locale/cs.json => locales/cs-CZ.json} (100%) rename src/static/{locale/de-de.json => locales/de-DE.json} (100%) rename src/static/{locale/es-es.json => locales/es-ES.json} (91%) rename src/static/{locale/fa.json => locales/fa-IR.json} (100%) rename src/static/{locale/fr-ca.json => locales/fr-CA.json} (100%) rename src/static/{locale/fr-fr.json => locales/fr-FR.json} (100%) rename src/static/{locale/it-it.json => locales/it-IT.json} (100%) rename src/static/{locale/ja-jp.json => locales/ja-JP.json} (100%) rename src/static/{locale/ko.json => locales/ko-KR.json} (100%) create mode 100644 src/static/locales/locale_manager.py rename src/static/{locale/language_template.json => locales/locale_template.json} (100%) rename src/static/{locale/nl-nl.json => locales/nl-NL.json} (100%) rename src/static/{locale/pl.json => locales/pl-PL.json} (100%) rename src/static/{locale/ru.json => locales/ru-RU.json} (100%) create mode 100644 src/static/locales/supported_locales.json rename src/static/{locale/sv-se.json => locales/sv-SE.json} (100%) rename src/static/{locale/th.json => locales/th-TH.json} (100%) rename src/static/{locale/tr-tr.json => locales/tr-TR.json} (100%) rename src/static/{locale/uk.json => locales/uk-UA.json} (100%) rename src/static/{locale/zh-cn.json => locales/zh-CN.json} (100%) rename src/static/{locale/zh-hk.json => locales/zh-HK.json} (100%) diff --git a/src/dashboard.py b/src/dashboard.py index 8265875..ee5d4ee 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -3001,9 +3001,9 @@ def API_Welcome_Finish(): class Locale: def __init__(self): - self.localePath = './static/locale/' + self.localePath = './static/locales/' self.activeLanguages = {} - with open(os.path.join(f"{self.localePath}active_languages.json"), "r") as f: + with open(os.path.join(f"{self.localePath}supported_locales.json"), "r") as f: self.activeLanguages = sorted(json.loads(''.join(f.readlines())), key=lambda x : x['lang_name']) def getLanguage(self) -> dict | None: diff --git a/src/static/locale/active_languages.json b/src/static/locale/active_languages.json deleted file mode 100644 index f790316..0000000 --- a/src/static/locale/active_languages.json +++ /dev/null @@ -1,112 +0,0 @@ -[ - { - "lang_id": "zh-cn", - "lang_name": "Chinese (Simplified)", - "lang_name_localized": "中文(简体)" - }, - { - "lang_id": "be", - "lang_name": "Belarusian", - "lang_name_localized": "Беларуская" - }, - { - "lang_id": "ca", - "lang_name": "Catalan", - "lang_name_localized": "Català" - }, - { - "lang_id": "zh-hk", - "lang_name": "Chinese (Traditional)", - "lang_name_localized": "中文(繁體)" - }, - { - "lang_id": "cs", - "lang_name": "Czech", - "lang_name_localized": "Česky" - }, - { - "lang_id": "nl-nl", - "lang_name": "Dutch", - "lang_name_localized": "Nederlands" - }, - { - "lang_id": "ar-sa", - "lang_name": "Arabic", - "lang_name_localized": "العربية" - }, - { - "lang_id": "en", - "lang_name": "English", - "lang_name_localized": "English" - }, - { - "lang_id": "fa", - "lang_name": "Farsi", - "lang_name_localized": "فارسی" - }, - { - "lang_id": "fr-ca", - "lang_name": "French (Quebec)", - "lang_name_localized": "Français (Québec)" - }, - { - "lang_id": "fr-fr", - "lang_name": "French (France)", - "lang_name_localized": "Français (France)" - }, - { - "lang_id": "de-de", - "lang_name": "German", - "lang_name_localized": "Deutsch" - }, - { - "lang_id": "it-it", - "lang_name": "Italian", - "lang_name_localized": "Italiano" - }, - { - "lang_id": "ja-jp", - "lang_name": "Japanese", - "lang_name_localized": "日本語" - }, - { - "lang_id": "ko", - "lang_name": "Korean", - "lang_name_localized": "한국어" - }, - { - "lang_id": "pl", - "lang_name": "Polish", - "lang_name_localized": "Polski" - }, - { - "lang_id": "ru", - "lang_name": "Russian", - "lang_name_localized": "Русский" - }, - { - "lang_id": "es-es", - "lang_name": "Spanish", - "lang_name_localized": "Español" - }, - { - "lang_id": "sv-se", - "lang_name": "Swedish", - "lang_name_localized": "Svenska" - }, - { - "lang_id": "th", - "lang_name": "Thai", - "lang_name_localized": "ภาษาไทย" - }, - { - "lang_id": "tr-tr", - "lang_name": "Turkish", - "lang_name_localized": "Türkçe" - }, - { - "lang_id": "uk", - "lang_name": "Ukrainian", - "lang_name_localized": "Українська" - } -] diff --git a/src/static/locale/verify_locale_files.py b/src/static/locale/verify_locale_files.py deleted file mode 100644 index c3279c9..0000000 --- a/src/static/locale/verify_locale_files.py +++ /dev/null @@ -1,62 +0,0 @@ -import json -active_languages = json.loads(open("active_languages.json", "r").read()) -language_template = json.loads(open("language_template.json", "r").read()) - -if __name__ == "__main__": - welcome = "WGDashboard Locale File Verification [by @donaldzou]" - print("="*(len(welcome) + 4)) - print(f"| {welcome} |") - print("="*(len(welcome) + 4)) - print() - print("Active Languages\n") - status = False - - for language in active_languages: - print(f"{language['lang_name']} | {language['lang_id']}") - - lang_ids = list(map(lambda x: x['lang_id'], active_languages)) - print() - - lang_id = "" - - while not status: - lang_id = input("Please enter the language ID to verify: ") - if lang_id not in lang_ids: - print(f'{lang_id} is not a valid language ID') - elif lang_id == 'en': - print(f'{lang_id} is not a editable language') - else: - status = True - - - with open(f"{lang_id}.json", "r") as f: - lang_file = json.load(f) - - # Identify missing and deprecated translations - missing_translation = [ - key for key in language_template - if key not in lang_file or not lang_file[key].strip() - ] - - deprecated_translation = [ - key for key in lang_file - if key not in language_template - ] - - with open(f"{lang_id}.json", "w") as f: - new_lang_file = dict(lang_file) - for key in missing_translation: - new_lang_file[key] = "" - - for key in deprecated_translation: - new_lang_file.pop(key) - - f.write(json.dumps(new_lang_file, ensure_ascii=False, indent='\t')) - - - print() - # Print missing translations - print(f"\t[Missing Translations] {len(missing_translation)} translation{'s' if len(missing_translation) > 1 else ''}") - # Print deprecated translations - print(f"\t[Deprecated Translations] {len(deprecated_translation)} translation{'s' if len(deprecated_translation) > 1 else ''}") - print(f"\t[Note] All missing translations are added into {lang_id}.json, all deprecated translations are removed from {lang_id}.json") \ No newline at end of file diff --git a/src/static/locale/ar-sa.json b/src/static/locales/ar-SA.json similarity index 100% rename from src/static/locale/ar-sa.json rename to src/static/locales/ar-SA.json diff --git a/src/static/locale/be.json b/src/static/locales/be-BY.json similarity index 100% rename from src/static/locale/be.json rename to src/static/locales/be-BY.json diff --git a/src/static/locale/ca.json b/src/static/locales/ca-ES.json similarity index 100% rename from src/static/locale/ca.json rename to src/static/locales/ca-ES.json diff --git a/src/static/locale/cs.json b/src/static/locales/cs-CZ.json similarity index 100% rename from src/static/locale/cs.json rename to src/static/locales/cs-CZ.json diff --git a/src/static/locale/de-de.json b/src/static/locales/de-DE.json similarity index 100% rename from src/static/locale/de-de.json rename to src/static/locales/de-DE.json diff --git a/src/static/locale/es-es.json b/src/static/locales/es-ES.json similarity index 91% rename from src/static/locale/es-es.json rename to src/static/locales/es-ES.json index e027bed..0e28c60 100644 --- a/src/static/locale/es-es.json +++ b/src/static/locales/es-ES.json @@ -1,312 +1,369 @@ { - "Welcome to": "Bienvenido", - "Username": "Usuario", - "Password": "Contraseña", - "OTP from your authenticator": "OTP de tu autentificador", - "Sign In": "Iniciar sesión", - "Signing In\\.\\.\\.": "Iniciando sesión...", - "Access Remote Server": "Acceder a servidor remoto", - "Server": "Servidor", - "Click": "Click", - "Pinging...": "Ping...", - "to add your server": "para agregar tu servidor", - "Server List": "Lista de Servidores", - "Sorry, your username or password is incorrect.": "Lo siento, tu usuario o contraseña no son correctos.", - "Home": "Home", - "Settings": "Ajustes", - "Tools": "Herramientas", - "Sign Out": "Salir", - "Checking for update...": "Buscando actualizaciones...", - "You're on the latest version": "Estás utilizando la última versión", - "WireGuard Configurations": "Configuraciones de Wireguard", - "You don't have any WireGuard configurations yet. Please check the configuration folder or change it in Settings. By default the folder is /etc/wireguard.": "Todavía no tienes configuraciones de WireGuard. Por favor, comprueba la carpeta de configuraciones o cámbiala en Ajustes. Por defecto, la carpeta es /etc/wireguard.", - "Configuration": "Configuración", - "Configurations": "Configuraciones", - "Peers Default Settings": "Ajustes por defecto de Peers", - "Dashboard Theme": "Tema del dashboard", - "Light": "Claro", - "Dark": "Oscuro", - "This will be changed globally, and will be apply to all peer's QR code and configuration file.": "Esto se va a cambiar de forma global, y se aplicará a todos los QRs y archivos de configuración de todos los Peers.", - "WireGuard Configurations Settings": "Ajustes de Configuración de Wireguard", - "Configurations Directory": "Carpeta de configuraciones", - "Remember to remove / at the end of your path. e.g /etc/wireguard": "Recuerda eliminar '/' al final de tu directorio. Por ejemplo, '/etc/wireguard'", - "WGDashboard Account Settings": "Ajustes de la cuenta de WGDashboard", - "Current Password": "Contraseña actual", - "New Password": "Nueva contraseña", - "Repeat New Password": "Repite la nueva contraseña", - "Update Password": "Actualizar contraseña", - "Multi-Factor Authentication \\(MFA\\)": "Autentificación de doble factor (MFA)", - "Reset": "Reset", - "Setup": "Setup", - "API Keys": "Clave API", - "API Key": "Clave API", - "Key": "Clave", - "Enabled": "Habilitado", - "Disabled": "Deshabilitado", - "No WGDashboard API Key": "Ninguna clave API WGDashboard", - "Expire At": "Expira en", - "Are you sure to delete this API key\\?": "¿Estás seguro de eliminar esta clave API?", - "Create API Key": "Crear una nueva clave API", - "When should this API Key expire\\?": "¿Cuándo debería expirar esta clave API?", - "Never Expire": "Nunca expira", - "Don't think that's a good idea": "No creo que esta sea una buena idea", - "Creating\\.\\.\\.": "Creando...", - "Create": "Crear", - "Status": "Estado", - "On": "Activo", - "Off": "Inactivo", - "Turning On\\.\\.\\.": "Activando...", - "Turning Off\\.\\.\\.": "Desactivando...", - "Address": "Dirección", - "Listen Port": "Puerto de escucha", - "Public Key": "Clave pública", - "Connected Peers": "Peer conectado", - "Total Usage": "Uso Total", - "Total Received": "Total Recibido", - "Total Sent": "Total Enviado", - "Peers Data Usage": "Uso de datos de Peers", - "Real Time Received Data Usage": "Datos recibidos en tiempo real", - "Real Time Sent Data Usage": "Datos enviados en tiempo real", - "Peer": "Peer", - "Peers": "Peers", - "Peer Settings": "Ajustes de Peers", - "Download All": "Descargar todo", - "Search Peers\\.\\.\\.": "Buscar Peers...", - "Display": "Mostrar", - "Sort By": "Ordenar por", - "Refresh Interval": "Intervalo de refresco", - "Name": "Nombre", - "Allowed IPs": "IPs permitidas", - "Restricted": "Restringido", - "(.*) Seconds": "$1 Segundos/i", + " file": " archivo", + "(.*) Available IP Address": "$1 Dirección IP disponible", "(.*) Minutes": "$1 Minutos/i", - "Configuration Settings": "Configuración", - "Peer Jobs": "Jobs del Peer", - "Active Jobs": "Jobs Activos", - "All Active Jobs": "Todos los Jobs Activos", - "Logs": "Logs", - "Private Key": "Clave privada", - "\\(Required for QR Code and Download\\)": "(Requerido para el código QR y Descarga)", - "\\(Required\\)": "(Requerido)", - "Endpoint Allowed IPs": "Direcciones IP del endpoint permitidas", - "DNS": "DNS", - "Optional Settings": "Ajustes opcionales", - "Pre-Shared Key": "Clave pre-compartida", - "MTU": "MTU", - "Persistent Keepalive": "Keepalive persistente", - "Reset Data Usage": "Resetear los datos de uso", - "Total": "Total", - "Sent": "Enviado", - "Received": "Recibido", - "Revert": "Revertir", - "Save Peer": "Guardar el Peer", - "QR Code": "Código QR", - "Schedule Jobs": "Planificar Job", - "Job": "Job", - "Job ID": "ID del Job", - "Unsaved Job": "Job sin guardar", - "This peer does not have any job yet\\.": "Este Peer no tiene jobs todavía.", - "if": "si", - "is": "es", - "then": "entonces", - "larger than": "mayor que", - "Date": "Fecha", - "Restrict Peer": "Limitar el Peer", - "Delete Peer": "Eliminar el Peer", - "Edit": "Modificar", - "Delete": "Eliminar", - "Deleting...": "Eliminando...", - "Cancel": "Cancelar", - "Save": "Guardar", - "No active job at the moment\\.": "No hay jobs activos en este momento.", - "Jobs Logs": "Logs del Job", - "Updated at": "Actualizado el", - "Refresh": "Refrescar", - "Filter": "Filtrar", - "Success": "Éxito", - "Failed": "Fallido", - "Log ID": "ID del log", - "Message": "Mensaje", - "Share Peer": "Compartir Peer", - "Currently the peer is not sharing": "Actualmente el Peer no está compartiendo", - "Sharing\\.\\.\\.": "Compartiendo...", - "Start Sharing": "Empezar intercambio", - "Stop Sharing\\.\\.\\.": "Interrumpir intercambio...", - "Stop Sharing": "Interrumpir intercambio", + "(.*) Seconds": "$1 Segundos/i", + "(.*) Used": "", + "(.*) is off": "$1 está desactivado", + "(.*) is on": "$1 está activo", + "([0-9].*) Backups?": "$1 Backups?", + "([0-9].*) Peers?": "¿$1 Peers?", + "([0-9]{1,}) Interfaces": "", + "([0-9]{1,}) Partitions": "", + "(v[0-9.]{1,}) is now available for update!": "$1 está disponible para actualizar!", + "1\\. Please scan the following QR Code to generate TOTP with your choice of authenticator": "1. Por favor escanea el siguiente código QR para generar un código TOTP con el autentificador que prefieras", + "2\\. Enter the TOTP generated by your authenticator to verify": "2. Inserta el código TOTP generado por tu autentificador para verificar", + "API Key": "Clave API", + "API Key created": "Clave API creada", + "API Key deleted": "Clave API eliminada", + "API Keys": "Clave API", + "API Keys function is failed to disable": "La funcionalidad de clave API no se ha habilitado", + "API Keys function is failed to enable": "La funcionalidad de clave API no se ha habilitado", + "API Keys function is successfully disabled": "La funcionalidad de clave API se ha deshabilitado con éxito", + "API Keys function is successfully enabled": "La funcionalidad de clave API se ha habilitado con éxito", + "Access Remote Server": "Acceder a servidor remoto", "Access Restricted": "Accesso Restringido", - "Restrict Access": "Restringir Acceso", - "Restricting\\.\\.\\.": "Restringiendo...", - "Allow Access": "Habilitar Acceso", - "Allowing Access\\.\\.\\.": "Habilitando el acceso...", - "Download \\& QR Code is not available due to no private key set for this peer": "La descarga y el código QR no están disponibles porque no se ha configurado una clave privada para este peer", + "Account Settings": "Ajustes de la cuenta", + "Active Jobs": "Jobs Activos", + "Add": "añadir", "Add Peers": "Agregar un Peer", + "Adding\\.\\.\\.": "Añadiendo...", + "Address": "Dirección", + "Advanced Options": "", + "All Active Jobs": "Todos los Jobs Activos", + "All connected peers will get disconnected": "Todos los peers conectados se desconectarán", + "Allow Access": "Habilitar Acceso", + "Allow access successfully": "Acceso permitido con éxito", + "Allowed IP already taken by another peer": "IP Permitida ya ha sido seleccionada por otro peer", + "Allowed IPs": "IPs permitidas", + "Allowed IPs Validation": "", + "Allowed IPs already taken by another peer": "IPs disponibles ya han sido seleccionadas por otro peer", + "Allowed IPs is invalid": "Las IPs Permitidas son inválidas", + "Allowing Access\\.\\.\\.": "Habilitando el acceso...", + "AmneziaWG Peer Setting": "", + "Appearance": "apariencia", + "Are you sure to delete": "¿Estás seguro de eliminar?", + "Are you sure to delete this API key\\?": "¿Estás seguro de eliminar esta clave API?", + "Are you sure to delete this backup\\?": "¿Estás seguro de que quieres borrar este backup?", + "Are you sure to delete this configuration\\?": "¿Estás seguro de eliminar esta configuración?", + "Are you sure to delete this peer\\?": "¿Estás seguro de eliminar este peer?", + "Are you sure to restore this backup\\?": "", + "Average / Min / Max Round Trip Time": "Media / Min / Max Redondeo Viaje Tiempo", + "Average RTT \\(ms\\)": "", + "Backup": "Backup", + "Backup & Restore": "Backups y Restaurar", + "Backup Date": "Fecha del backup", + "Backup not selected": "Backup no seleccionado", + "Both configuration file \\(\\.conf\\) and database table related to this configuration will get deleted": "Ambos ficheros de configuración (.conf) y de la tabla de la base de datos relacionados con esta configuración serán eliminados", "Bulk Add": "Agregar múltiple", "By adding peers by bulk, each peer's name will be auto generated, and Allowed IP will be assign to the next available IP\\.": "Agregando Peers de forma múltiple, los nombres de los peers se generarán de forma automática, y la IP permitida se asignará en función de la siguiente IP disponible.", - "How many peers you want to add\\?": "¿Cuántos peers quieres añadir?", - "You can add up to (.*) peers": "Puedes añadir hasta $1 peers", - "Use your own Private and Public Key": "Usa tu propia clave pública y privada", - "Enter IP Address/CIDR": "ntroduce la dirección IP/CIDR", - "IP Address/CIDR": "Dirección IP/CIDR", - "or": "o", - "Pick Available IP": "Selecciona una IP disponible", - "No available IP containing": "Ninguna IP disponible que contenga", - "Add": "añadir", - "Adding\\.\\.\\.": "Añadiendo...", - "Failed to check available update": "Error al buscar actualizaciones disponibles", - "Nice to meet you!": "Encantado de conocerte", - "Please fill in the following fields to finish setup": "Por favor rellena los siguientes campos para finalizar el setup", - "Create an account": "Crea una cuenta", - "Enter an username you like": "Introduce un nombre de usuario", - "Enter a password": "Introduce una contraseña", - "\\(At least 8 characters and make sure is strong enough!\\)": "(Al menos 8 caracteres y asegúrate de que sea suficientemente fuerte)", - "Confirm password": "Confirma la contraseña", - "Next": "Siguiente", - "Saving\\.\\.\\.": "Guardando...", - "1\\. Please scan the following QR Code to generate TOTP with your choice of authenticator": "1. Por favor escanea el siguiente código QR para generar un código TOTP con el autentificador que prefieras", - "Or you can click the link below:": "O puedes hacer click en el siguiente enlace:", - "2\\. Enter the TOTP generated by your authenticator to verify": "2. Inserta el código TOTP generado por tu autentificador para verificar", - "TOTP verified!": "¡TOTP Verificado!", - "I don't need MFA": "No necesito MFA", + "CPU": "", + "CPU Usage": "", + "Cancel": "Cancelar", + "Checking backups...": "Comprobando backups...", + "Checking for update...": "Buscando actualizaciones...", + "Clear Selection": "Deseleccionar", + "Click": "Click", + "Click to change a backup": "click para cambiar el backup", "Complete": "Completado", - "(v[0-9.]{1,}) is now available for update!": "$1 está disponible para actualizar!", - "Current Version:": "Versión actual:", - "Oh no\\.\\.\\. This link is either expired or invalid\\.": "Oh no... Este link ha expirado o es inválido.", - "Scan QR Code with the WireGuard App to add peer": "Escanea el código QR con la App Wireguard para añadir el peer", - "or click the button below to download the ": "o haz click en el botón de abajo para descargar el ", - " file": " archivo", - "FROM ": "DE ", - "(.*) is on": "$1 está activo", - "(.*) is off": "$1 está desactivado", - "Allowed IPs is invalid": "Las IPs Permitidas son inválidas", - "Peer created successfully": "Peer creado con éxito", - "Please fill in all required box": "Por favor, rellena las casillas requeridas", - "Please specify amount of peers you want to add": "Por favor, especifica la cantidad de peers que quieres añadir", - "No more available IP can assign": "No hay más IPs disponibles que asignar", - "The maximum number of peers can add is (.*)": "El número máximo de peers que se pueden añadir es $1", - "Generating key pairs by bulk failed": "Generación de key pairs por Añadir Múltiples falló", - "Failed to add peers in bulk": "Error al añadir múltiples peers", - "This peer already exist": "Este peer ya existe", - "This IP is not available: (.*)": "Esta IP no está disponible: $1", - "Configuration does not exist": "La configuración no existe", - "Peer does not exist": "El peer no existe", - "Please provide a valid configuration name": "Por favor, introduce un nombre de configuración válido", - "Peer saved": "Peer guardado", - "Allowed IPs already taken by another peer": "IPs disponibles ya han sido seleccionadas por otro peer", - "Endpoint Allowed IPs format is incorrect": "El formato de las IPs disponibles no es correcto", - "DNS format is incorrect": "El formato del DNS no es correcto", - "MTU format is not correct": "El formato del MTU no es correcto", - "Persistent Keepalive format is not correct": "El formato de Keepalive persistente no es correcto", - "Private key does not match with the public key": "La clave privada no corresponde con la clave pública", - "Update peer failed when updating Pre-Shared Key": "Actualización del peer falló cuando se actualizó la clave pre-compartida", - "Update peer failed when updating Allowed IPs": "Actualización del peer falló cuando se actualizó las IPs Permitidas", - "Update peer failed when saving the configuration": "Actualización del peer falló cuando se guardaba la configuración", - "Peer data usage reset successfully": "Los datos del uso del peer se restablecieron con éxito", - "Peer download started": "Descarga del peer comenzada", - "Please specify one or more peers": "Por favor, especifica uno o más peers", - "Share link failed to create. Reason: (.*)": "Creación del link para compartir fallida. Motivo: $1", - "Link expire date updated": "Actualizada la fecha de expiración del link", - "Link expire date failed to update. Reason: (.*)": "Actualización de la fecha de expiración fallida. Motivo: $1", - "Peer job saved": "Job del Peer guardado", - "Please specify job": "Por favor, especifica un job", - "Please specify peer and configuration": "Por favor, especifica peer y configuración", - "Peer job deleted": "Job del Peer eliminado", - "API Keys function is successfully enabled": "La funcionalidad de clave API se ha habilitado con éxito", - "API Keys function is successfully disabled": "La funcionalidad de clave API se ha deshabilitado con éxito", - "API Keys function is failed to enable": "La funcionalidad de clave API no se ha habilitado", - "API Keys function is failed to disable": "La funcionalidad de clave API no se ha habilitado", - "WGDashboard API Keys function is disabled": "La funcionalidad de clave API de WGDashboard está deshabilitada", - "WireGuard configuration path saved": "Guardado el destino de la configuración de Wireguard", - "API Key deleted": "Clave API eliminada", - "API Key created": "Clave API creada", - "Sign in session ended, please sign in again": "La sesión actual ha caducado, por favor, accede de nuevo", - "Please specify an IP Address (v4/v6)": "Por favor, especificado dirección IP (v4/v6)", - "Please provide ipAddress and count": "Por favor, especifica dirección IP y cuenta", - "Please provide ipAddress": "Por favor, especifica dirección IP", - "Dashboard Language": "Idioma del Dashboard", - "Dashboard language update failed": "Imposible actualizar el idioma del Dashboard", - "Peer Remote Endpoint": "Endpoint remoto del Peer", - "New Configuration": "Nueva configuración", + "Configuration": "Configuración", + "Configuration File": "", "Configuration Name": "Nombre de la configuración", - "Configuration name is invalid. Possible reasons:": "El nombre de la configuración es inválido. Motivo:", + "Configuration Settings": "Configuración", + "Configuration deleted": "Configuración eliminada", + "Configuration does not exist": "La configuración no existe", "Configuration name already exist\\.": "El nombre de la configuración ya existe.", "Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen\\.": "El nombre de la configuración solo puede contener 15 letras mayúsculas o minúsculas, números, barrabaja, símbolos de igual, suma, puntos y guiones.", - "Invalid Port": "Puerto inválido", - "Save Configuration": "Guardar la configuración", - "IP Address/CIDR is invalid": "La IP/CIDR no es válida", - "IP Address": "Dirección IP", - "Enter IP Address / Hostname": "Introduce una dirección IP / Hostname", - "IP Address / Hostname": "Dirección IP / Hostname", - "Dashboard IP Address \\& Listen Port": "Dirección IP del dashboard y puerto", - "Count": "Cuenta", - "Geolocation": "Geolocalización", - "Is Alive": "Está Activo", - "Average / Min / Max Round Trip Time": "Media / Min / Max Redondeo Viaje Tiempo", - "Sent / Received / Lost Package": "Enviado / Recibido / Paquetes perdidos", - "Manual restart of WGDashboard is needed to apply changes on IP Address and Listen Port": "Un reset manual de WGDashboard es necesario para aplicar los cambios en la dirección IP y el puerto", - "Restore Configuration": "Restablecer configuración", - "Step (.*)": "Paso $1", - "Select a backup you want to restore": "Selecciona el backup que quieres restaurar", - "Click to change a backup": "click para cambiar el backup", - "Selected Backup": "Backup seleccionado", - "You don't have any configuration to restore": "No tienes ninguna configuración que restaurar", - "Help": "Ayuda", - "Backup": "Backup", - "([0-9].*) Backups?": "$1 Backups?", - "Yes": "Sí", - "No": "No", - "Backup not selected": "Backup no seleccionado", - "Confirm \\& edit restore information": "Confirmar y editar la información de restauración", - "(.*) Available IP Address": "$1 Dirección IP disponible", - "Database File": "Fichero de base de datos", - "Contain": "Contiene", - "Restricted Peers?": "¿Peers restringidos?", - "Restore": "Restaurar", - "Restoring": "Restaurando", - "WGDashboard Settings": "Ajustes de WGDashboard", - "Peers Settings": "Ajustes de Peers", - "WireGuard Configuration Settings": "Ajustes de Configuraciones de Wireguard", - "Appearance": "apariencia", - "Theme": "Tema", - "Language": "Idioma", - "Account Settings": "Ajustes de la cuenta", - "Peer Default Settings": "Ajustes por defecto del peer", - "Toggle When Start Up": "Activar en Start Up", - "Other Settings": "Otros ajustes", - "Select Peers": "Seleccionar peers", - "Backup & Restore": "Backups y Restaurar", - "Delete Configuration": "Eliminar Configuración", - "Create Backup": "Crear Backup", - "No backup yet, click the button above to create backup\\.": "No hay backups todavía, presiona el botón de arriba para crear un backup.", - "Are you sure to delete this backup\\?": "¿Estás seguro de que quieres borrar este backup?", - "Are you sure to restore this backup?\\": "¿Estás seguro de que quieres restaurar este backup?", - "Backup Date": "Fecha del backup", - "File": "Archivo", - "Are you sure to delete this configuration\\?": "¿Estás seguro de eliminar esta configuración?", - "Once you deleted this configuration\\:": "Una vez eliminada esta configuración:", - "All connected peers will get disconnected": "Todos los peers conectados se desconectarán", - "Both configuration file \\(\\.conf\\) and database table related to this configuration will get deleted": "Ambos ficheros de configuración (.conf) y de la tabla de la base de datos relacionados con esta configuración serán eliminados", - "Checking backups...": "Comprobando backups...", - "This configuration have ([0-9].*) backups": "Esta configuración tiene $1 backups", - "This configuration have no backup": "Esta configuración no tiene backups", - "If you're sure, please type in the configuration name below and click Delete": "Si estás seguro, introduce el nombre de la configuración y presiona eliminar", - "Select All": "Seleccionar todo", - "Clear Selection": "Deseleccionar", - "([0-9].*) Peers?": "¿$1 Peers?", - "Downloading": "Descargando", - "Download Finished": "Descarga finalizada", - "Done": "Hecho", - "Are you sure to delete": "¿Estás seguro de eliminar?", - "Are you sure to delete this peer\\?": "¿Estás seguro de eliminar este peer?", - "Configuration deleted": "Configuración eliminada", - "Configuration saved": "Configuración guardada", - "WGDashboard language update failed": "La actualización de idioma de WGDashboard falló", + "Configuration name is invalid. Possible reasons:": "El nombre de la configuración es inválido. Motivo:", "Configuration restored": "Configuración restaurada", - "Allowed IP already taken by another peer": "IP Permitida ya ha sido seleccionada por otro peer", - "Failed to allow access of peer (.*)": "Fallo de habilitar acceso del peer $1", - "Failed to save configuration through WireGuard": "Fallo de salvar configuración a través de WireGuard", - "Allow access successfully": "Acceso permitido con éxito", + "Configuration saved": "Configuración guardada", + "Configurations": "Configuraciones", + "Configurations Directory": "Carpeta de configuraciones", + "Confirm \\& edit restore information": "Confirmar y editar la información de restauración", + "Confirm password": "Confirma la contraseña", + "Connected Peers": "Peer conectado", + "Contain": "Contiene", + "Count": "Cuenta", + "Create": "Crear", + "Create API Key": "Crear una nueva clave API", + "Create Backup": "Crear Backup", + "Create an account": "Crea una cuenta", + "Creating\\.\\.\\.": "Creando...", + "Current Password": "Contraseña actual", + "Current Version:": "Versión actual:", + "Currently the peer is not sharing": "Actualmente el Peer no está compartiendo", + "DNS": "DNS", + "DNS format is incorrect": "El formato del DNS no es correcto", + "Danger Zone": "", + "Dark": "Oscuro", + "Dashboard IP Address \\& Listen Port": "Dirección IP del dashboard y puerto", + "Dashboard Language": "Idioma del Dashboard", + "Dashboard Theme": "Tema del dashboard", + "Dashboard language update failed": "Imposible actualizar el idioma del Dashboard", + "Database File": "Fichero de base de datos", + "Date": "Fecha", + "Delete": "Eliminar", + "Delete Configuration": "Eliminar Configuración", + "Delete Peer": "Eliminar el Peer", + "Delete current configuration's database table and \\.conf file": "", "Deleted ([0-9]{1,}) peer\\(s\\)": "Eliminados $1 peer(s)", "Deleted ([0-9]{1,}) peer\\(s\\) successfully. Failed to delete ([0-9]{1,}) peer\\(s\\)": "Eliminados $1 peer(s) con éxito. Fallo al eliminar $2 peer(s)", + "Deleting...": "Eliminando...", + "Disabled": "Deshabilitado", + "Discord Server": "", + "Display": "Mostrar", + "Display As": "", + "Don't think that's a good idea": "No creo que esta sea una buena idea", + "Done": "Hecho", + "Download": "", + "Download All": "Descargar todo", + "Download Finished": "Descarga finalizada", + "Download \\& QR Code is not available due to no private key set for this peer": "La descarga y el código QR no están disponibles porque no se ha configurado una clave privada para este peer", + "Downloading": "Descargando", + "Duplicate current configuration's database table and \\.conf file with the new name": "", + "Edit": "Modificar", + "Edit Raw Configuration File": "", + "Email Account": "", + "Email Body Template": "", + "Email sent successfully!": "", + "Enabled": "Habilitado", + "Encryption": "", + "Endpoint Allowed IPs": "Direcciones IP del endpoint permitidas", + "Endpoint Allowed IPs format is incorrect": "El formato de las IPs disponibles no es correcto", + "Enter IP Address / Hostname": "Introduce una dirección IP / Hostname", + "Enter IP Address/CIDR": "ntroduce la dirección IP/CIDR", + "Enter a password": "Introduce una contraseña", + "Enter an username you like": "Introduce un nombre de usuario", + "Expire At": "Expira en", + "FROM ": "DE ", + "Failed": "Fallido", + "Failed to add peers in bulk": "Error al añadir múltiples peers", + "Failed to allow access of peer (.*)": "Fallo de habilitar acceso del peer $1", + "Failed to check available update": "Error al buscar actualizaciones disponibles", + "Failed to save configuration through WireGuard": "Fallo de salvar configuración a través de WireGuard", + "File": "Archivo", + "Filter": "Filtrar", + "Generating key pairs by bulk failed": "Generación de key pairs por Añadir Múltiples falló", + "Geolocation": "Geolocalización", + "Grid": "", + "Help": "Ayuda", + "Home": "Home", + "Hop": "", + "How many peers you want to add\\?": "¿Cuántos peers quieres añadir?", + "I don't need MFA": "No necesito MFA", + "IP Address": "Dirección IP", + "IP Address / Hostname": "Dirección IP / Hostname", + "IP Address/CIDR": "Dirección IP/CIDR", + "IP Address/CIDR is invalid": "La IP/CIDR no es válida", + "If you're sure, please type in the configuration name below and click Delete": "Si estás seguro, introduce el nombre de la configuración y presiona eliminar", + "Include configuration file as an attachment": "", + "Invalid Port": "Puerto inválido", + "Is Alive": "Está Activo", + "Job": "Job", + "Job ID": "ID del Job", + "Jobs Logs": "Logs del Job", + "Key": "Clave", + "Language": "Idioma", + "Light": "Claro", + "Link expire date failed to update. Reason: (.*)": "Actualización de la fecha de expiración fallida. Motivo: $1", + "Link expire date updated": "Actualizada la fecha de expiración del link", + "List": "", + "Listen Port": "Puerto de escucha", + "Live Preview": "", + "Log ID": "ID del log", + "Logs": "Logs", + "MTU": "MTU", + "MTU format is not correct": "El formato del MTU no es correcto", + "Manual restart of WGDashboard is needed to apply changes on IP Address and Listen Port": "Un reset manual de WGDashboard es necesario para aplicar los cambios en la dirección IP y el puerto", + "Max RTT \\(ms\\)": "", + "Memory": "", + "Memory Usage": "", + "Message": "Mensaje", + "Min RTT \\(ms\\)": "", + "Multi-Factor Authentication \\(MFA\\)": "Autentificación de doble factor (MFA)", + "Name": "Nombre", + "Network": "", + "Never Expire": "Nunca expira", + "New Configuration": "Nueva configuración", + "New Password": "Nueva contraseña", + "Next": "Siguiente", + "Nice to meet you!": "Encantado de conocerte", + "No": "No", + "No Encryption": "", + "No WGDashboard API Key": "Ninguna clave API WGDashboard", + "No active job at the moment\\.": "No hay jobs activos en este momento.", + "No available IP containing": "Ninguna IP disponible que contenga", + "No backup yet, click the button above to create backup\\.": "No hay backups todavía, presiona el botón de arriba para crear un backup.", + "No more available IP can assign": "No hay más IPs disponibles que asignar", + "OTP from your authenticator": "OTP de tu autentificador", + "Off": "Inactivo", + "Official Documentation": "", + "Oh no\\.\\.\\. This link is either expired or invalid\\.": "Oh no... Este link ha expirado o es inválido.", + "On": "Activo", + "Once you deleted this configuration\\:": "Una vez eliminada esta configuración:", + "Open File": "", + "Optional Settings": "Ajustes opcionales", + "Or you can click the link below:": "O puedes hacer click en el siguiente enlace:", + "Other Settings": "Otros ajustes", + "Password": "Contraseña", + "Peer": "Peer", + "Peer Configuration File": "", + "Peer Default Settings": "Ajustes por defecto del peer", + "Peer Jobs": "Jobs del Peer", + "Peer Remote Endpoint": "Endpoint remoto del Peer", + "Peer Settings": "Ajustes de Peers", + "Peer created successfully": "Peer creado con éxito", + "Peer data usage reset successfully": "Los datos del uso del peer se restablecieron con éxito", + "Peer does not exist": "El peer no existe", + "Peer download started": "Descarga del peer comenzada", + "Peer job deleted": "Job del Peer eliminado", + "Peer job saved": "Job del Peer guardado", + "Peer saved": "Peer guardado", + "Peers": "Peers", + "Peers Data Usage": "Uso de datos de Peers", + "Peers Default Settings": "Ajustes por defecto de Peers", + "Peers Settings": "Ajustes de Peers", + "Persistent Keepalive": "Keepalive persistente", + "Persistent Keepalive format is not correct": "El formato de Keepalive persistente no es correcto", + "Pick Available IP": "Selecciona una IP disponible", + "Pinging...": "Ping...", + "Please fill in all required box": "Por favor, rellena las casillas requeridas", + "Please fill in the following fields to finish setup": "Por favor rellena los siguientes campos para finalizar el setup", + "Please provide a valid configuration name": "Por favor, introduce un nombre de configuración válido", + "Please provide ipAddress": "Por favor, especifica dirección IP", + "Please provide ipAddress and count": "Por favor, especifica dirección IP y cuenta", + "Please specify amount of peers you want to add": "Por favor, especifica la cantidad de peers que quieres añadir", + "Please specify an IP Address (v4/v6)": "Por favor, especificado dirección IP (v4/v6)", + "Please specify job": "Por favor, especifica un job", + "Please specify one or more peers": "Por favor, especifica uno o más peers", + "Please specify peer and configuration": "Por favor, especifica peer y configuración", + "Port": "", + "Pre-Shared Key": "Clave pre-compartida", + "Private Key": "Clave privada", + "Private key does not match with the public key": "La clave privada no corresponde con la clave pública", + "Processes": "", + "Protocol": "", + "Public Key": "Clave pública", + "QR Code": "Código QR", + "Ready": "", + "Real Time Received Data Usage": "Datos recibidos en tiempo real", + "Real Time Sent Data Usage": "Datos enviados en tiempo real", + "Received": "Recibido", + "Refresh": "Refrescar", + "Refresh Interval": "Intervalo de refresco", + "Remember to remove / at the end of your path. e.g /etc/wireguard": "Recuerda eliminar '/' al final de tu directorio. Por ejemplo, '/etc/wireguard'", + "Repeat New Password": "Repite la nueva contraseña", + "Reset": "Reset", + "Reset Data Usage": "Resetear los datos de uso", + "Restore": "Restaurar", + "Restore Configuration": "Restablecer configuración", + "Restoring": "Restaurando", + "Restrict Access": "Restringir Acceso", + "Restrict Peer": "Limitar el Peer", + "Restricted": "Restringido", "Restricted ([0-9]{1,}) peer\\(s\\)": "Restringidos $1 peer(s)", - "Restricted ([0-9]{1,}) peer\\(s\\) successfully. Failed to restrict ([0-9]{1,}) peer\\(s\\)": "Restringidos $1 peer(s) con éxito. Fallo al restringir $2 peer(s)" + "Restricted ([0-9]{1,}) peer\\(s\\) successfully. Failed to restrict ([0-9]{1,}) peer\\(s\\)": "Restringidos $1 peer(s) con éxito. Fallo al restringir $2 peer(s)", + "Restricted Peers?": "¿Peers restringidos?", + "Restricting\\.\\.\\.": "Restringiendo...", + "Revert": "Revertir", + "Save": "Guardar", + "Save Configuration": "Guardar la configuración", + "Save Peer": "Guardar el Peer", + "Saving\\.\\.\\.": "Guardando...", + "Scan QR Code with the WireGuard App to add peer": "Escanea el código QR con la App Wireguard para añadir el peer", + "Schedule Jobs": "Planificar Job", + "Search": "", + "Search Peers\\.\\.\\.": "Buscar Peers...", + "Select All": "Seleccionar todo", + "Select Peers": "Seleccionar peers", + "Select a backup you want to restore": "Selecciona el backup que quieres restaurar", + "Selected Backup": "Backup seleccionado", + "Send": "", + "Send From": "", + "Send Test Email": "", + "Sending\\.\\.\\.": "", + "Sent": "Enviado", + "Sent / Received / Lost Package": "Enviado / Recibido / Paquetes perdidos", + "Server": "Servidor", + "Server List": "Lista de Servidores", + "Settings": "Ajustes", + "Setup": "Setup", + "Share Peer": "Compartir Peer", + "Share link failed to create. Reason: (.*)": "Creación del link para compartir fallida. Motivo: $1", + "Share with Email": "", + "Sharing\\.\\.\\.": "Compartiendo...", + "Sign In": "Iniciar sesión", + "Sign Out": "Salir", + "Sign in session ended, please sign in again": "La sesión actual ha caducado, por favor, accede de nuevo", + "Signing In\\.\\.\\.": "Iniciando sesión...", + "Sorry, your username or password is incorrect.": "Lo siento, tu usuario o contraseña no son correctos.", + "Sort By": "Ordenar por", + "Start Sharing": "Empezar intercambio", + "Status": "Estado", + "Step (.*)": "Paso $1", + "Stop Sharing": "Interrumpir intercambio", + "Stop Sharing\\.\\.\\.": "Interrumpir intercambio...", + "Storage": "", + "Success": "Éxito", + "Swap Memory": "", + "Swap Memory Usage": "", + "System Status": "", + "TOTP verified!": "¡TOTP Verificado!", + "Table": "", + "The maximum number of peers can add is (.*)": "El número máximo de peers que se pueden añadir es $1", + "Theme": "Tema", + "This IP is not available: (.*)": "Esta IP no está disponible: $1", + "This configuration have ([0-9].*) backups": "Esta configuración tiene $1 backups", + "This configuration have no backup": "Esta configuración no tiene backups", + "This peer already exist": "Este peer ya existe", + "This peer does not have any job yet\\.": "Este Peer no tiene jobs todavía.", + "This will be changed globally, and will be apply to all peer's QR code and configuration file.": "Esto se va a cambiar de forma global, y se aplicará a todos los QRs y archivos de configuración de todos los Peers.", + "To update this configuration's name, WGDashboard will execute the following operations:": "", + "Toggle When Start Up": "Activar en Start Up", + "Tools": "Herramientas", + "Total": "Total", + "Total Received": "Total Recibido", + "Total Sent": "Total Enviado", + "Total Usage": "Uso Total", + "Turning Off\\.\\.\\.": "Desactivando...", + "Turning On\\.\\.\\.": "Activando...", + "Unsaved Job": "Job sin guardar", + "Untitled Peer": "", + "Update Name": "", + "Update Password": "Actualizar contraseña", + "Update peer failed when saving the configuration": "Actualización del peer falló cuando se guardaba la configuración", + "Update peer failed when updating Allowed IPs": "Actualización del peer falló cuando se actualizó las IPs Permitidas", + "Update peer failed when updating Pre-Shared Key": "Actualización del peer falló cuando se actualizó la clave pre-compartida", + "Updated at": "Actualizado el", + "Use your own Private and Public Key": "Usa tu propia clave pública y privada", + "Username": "Usuario", + "WGDashboard API Keys function is disabled": "La funcionalidad de clave API de WGDashboard está deshabilitada", + "WGDashboard Account Settings": "Ajustes de la cuenta de WGDashboard", + "WGDashboard Settings": "Ajustes de WGDashboard", + "WGDashboard language update failed": "La actualización de idioma de WGDashboard falló", + "Welcome to": "Bienvenido", + "What\\'s the body\\?": "", + "What\\'s the subject\\?": "", + "When should this API Key expire\\?": "¿Cuándo debería expirar esta clave API?", + "Who are you sending to\\?": "", + "WireGuard Configuration Settings": "Ajustes de Configuraciones de Wireguard", + "WireGuard Configurations": "Configuraciones de Wireguard", + "WireGuard Configurations Settings": "Ajustes de Configuración de Wireguard", + "WireGuard configuration path saved": "Guardado el destino de la configuración de Wireguard", + "Yes": "Sí", + "You can add up to (.*) peers": "Puedes añadir hasta $1 peers", + "You can visit our: ": "", + "You don't have any WireGuard configurations yet. Please check the configuration folder or change it in Settings. By default the folder is /etc/wireguard.": "Todavía no tienes configuraciones de WireGuard. Por favor, comprueba la carpeta de configuraciones o cámbiala en Ajustes. Por defecto, la carpeta es /etc/wireguard.", + "You don't have any configuration to restore": "No tienes ninguna configuración que restaurar", + "You're on the latest version": "Estás utilizando la última versión", + "\\(At least 8 characters and make sure is strong enough!\\)": "(Al menos 8 caracteres y asegúrate de que sea suficientemente fuerte)", + "\\(Required for QR Code and Download\\)": "(Requerido para el código QR y Descarga)", + "\\(Required\\)": "(Requerido)", + "if": "si", + "is": "es", + "larger than": "mayor que", + "or": "o", + "or click the button below to download the ": "o haz click en el botón de abajo para descargar el ", + "then": "entonces", + "to add your server": "para agregar tu servidor" } \ No newline at end of file diff --git a/src/static/locale/fa.json b/src/static/locales/fa-IR.json similarity index 100% rename from src/static/locale/fa.json rename to src/static/locales/fa-IR.json diff --git a/src/static/locale/fr-ca.json b/src/static/locales/fr-CA.json similarity index 100% rename from src/static/locale/fr-ca.json rename to src/static/locales/fr-CA.json diff --git a/src/static/locale/fr-fr.json b/src/static/locales/fr-FR.json similarity index 100% rename from src/static/locale/fr-fr.json rename to src/static/locales/fr-FR.json diff --git a/src/static/locale/it-it.json b/src/static/locales/it-IT.json similarity index 100% rename from src/static/locale/it-it.json rename to src/static/locales/it-IT.json diff --git a/src/static/locale/ja-jp.json b/src/static/locales/ja-JP.json similarity index 100% rename from src/static/locale/ja-jp.json rename to src/static/locales/ja-JP.json diff --git a/src/static/locale/ko.json b/src/static/locales/ko-KR.json similarity index 100% rename from src/static/locale/ko.json rename to src/static/locales/ko-KR.json diff --git a/src/static/locales/locale_manager.py b/src/static/locales/locale_manager.py new file mode 100644 index 0000000..b1db989 --- /dev/null +++ b/src/static/locales/locale_manager.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +import json +import sys +from pathlib import Path +from typing import Dict, List, Tuple +from dataclasses import dataclass + + +@dataclass +class Language: + """Represents a language configuration.""" + lang_id: str + lang_name: str + + @classmethod + def from_dict(cls, data: Dict) -> 'Language': + """Create Language instance from dictionary.""" + return cls( + lang_id=data.get('lang_id', ''), + lang_name=data.get('lang_name', '') + ) + + +@dataclass +class TranslationStats: + """Statistics about translation verification.""" + missing_count: int + deprecated_count: int + total_keys: int + + @property + def completion_percentage(self) -> float: + """Calculate completion percentage.""" + if self.total_keys == 0: + return 0.0 + + completed = self.total_keys - self.missing_count + + return (completed / self.total_keys) * 100 + + +class LocaleManager: + """Manages locale files and translation verification.""" + + SUPPORTED_LOCALES_FILE = "supported_locales.json" + LOCALE_TEMPLATE_FILE = "locale_template.json" + + def __init__(self): + self.supported_locales: List[Language] = [] + self.locale_template: Dict[str, str] = {} + self._load_configuration() + + def _load_configuration(self) -> None: + """Load active languages and template configuration.""" + try: + + self.supported_locales = self._load_supported_locales() + self.locale_template = self._load_locale_template() + + except FileNotFoundError as e: + print(f"[✗] Configuration file not found: {e}") + sys.exit(1) + + except json.JSONDecodeError as e: + print(f"[✗] Invalid JSON in configuration file: {e}") + sys.exit(1) + + def _load_supported_locales(self) -> List[Language]: + """Load active languages from JSON file.""" + config_path = Path(self.SUPPORTED_LOCALES_FILE) + + if not config_path.exists(): + raise FileNotFoundError( + f"Active languages file not found: {config_path}" + ) + + with open(config_path, 'r', encoding='utf-8') as file: + data = json.load(file) + + return [Language.from_dict(lang_data) for lang_data in data] + + def _load_locale_template(self) -> Dict[str, str]: + """Load language template from JSON file.""" + template_path = Path(self.LOCALE_TEMPLATE_FILE) + + if not template_path.exists(): + raise FileNotFoundError( + f"Language template file not found: {template_path}" + ) + + with open(template_path, 'r', encoding='utf-8') as file: + return json.load(file) + + def _load_language_file(self, lang_id: str) -> Dict[str, str]: + """Load specific language file.""" + lang_path = Path(f"{lang_id}.json") + + if not lang_path.exists(): + raise FileNotFoundError(f"Language file not found: {lang_path}") + + with open(lang_path, 'r', encoding='utf-8') as file: + return json.load(file) + + def _save_language_file(self, lang_id: str, lang_data: Dict[str, str]) -> None: + """Save language file with proper formatting.""" + lang_path = Path(f"{lang_id}.json") + + with open(lang_path, 'w', encoding='utf-8') as file: + json.dump( + lang_data, + file, + ensure_ascii=False, + indent='\t', + sort_keys=True + ) + + def get_language_ids(self) -> List[str]: + """Get list of all available language IDs.""" + return [lang.lang_id for lang in self.supported_locales] + + def validate_language_id(self, lang_id: str) -> Tuple[bool, str]: + """ + Validate language ID. + + Returns: + Tuple of (is_valid, error_message) + """ + available_ids = self.get_language_ids() + + if lang_id not in available_ids: + return False, f"'{lang_id}' is not a valid language ID" + + return True, "" + + def analyze_translations(self, lang_id: str) -> Tuple[List[str], List[str], TranslationStats]: + """ + Analyze translation file for missing and deprecated keys. + + Returns: + Tuple of (missing_keys, deprecated_keys, stats) + """ + try: + lang_file = self._load_language_file(lang_id) + + except FileNotFoundError: + print( + f"[!] Language file {lang_id}.json not found. Creating new file..." + ) + lang_file = {} + + # Find missing translations + missing_translations = [ + key for key in self.locale_template + if key not in lang_file or not lang_file[key].strip() + ] + + # Find deprecated translations + deprecated_translations = [ + key for key in lang_file + if key not in self.locale_template + ] + + # Calculate statistics + stats = TranslationStats( + missing_count=len(missing_translations), + deprecated_count=len(deprecated_translations), + total_keys=len(self.locale_template) + ) + + return missing_translations, deprecated_translations, stats + + def fix_translation_file(self, lang_id: str) -> TranslationStats: + """ + Fix translation file by adding missing keys and removing deprecated ones. + + Returns: + TranslationStats with the changes made + """ + try: + lang_file = self._load_language_file(lang_id) + + except FileNotFoundError: + lang_file = {} + + missing_translations, deprecated_translations, stats = self.analyze_translations( + lang_id + ) + + # Create new language file + new_lang_file = dict(lang_file) + + # Add missing translations with empty values + for key in missing_translations: + new_lang_file[key] = "" + + # Remove deprecated translations + for key in deprecated_translations: + new_lang_file.pop(key, None) + + # Save updated file + self._save_language_file(lang_id, new_lang_file) + + return stats + + def display_header(self) -> None: + """Display application header.""" + title = "WGDashboard Locale File Manager [by @donaldzou]" + border = "=" * (len(title) + 4) + + print(border) + print(f"| {title} |") + print(border) + print() + + def display_available_languages(self) -> None: + """Display available languages in a formatted table.""" + print("[i] Available languages") + print("-" * 50) + + for lang in self.supported_locales: + print(f"{lang.lang_name:<25} | {lang.lang_id}") + + print() + + def display_translation_results(self, lang_id: str, stats: TranslationStats) -> None: + """Display translation verification results.""" + print(f"[#] Translation analysis for '{lang_id}'") + print("-" * 50) + + print(f" [-] Total keys: {stats.total_keys}") + print(f" [✗] Missing translations: {stats.missing_count}") + print(f" [*] Deprecated translations: {stats.deprecated_count}") + print(f" [✓] Completion: {stats.completion_percentage:.1f}%") + + if stats.missing_count > 0 or stats.deprecated_count > 0: + print(f"\n [i] File {lang_id}.json has been updated:") + print(f" • Missing translations added (empty values)") + print(f" • Deprecated translations removed") + else: + print(f"\n Perfect! No issues found in {lang_id}.json") + + print() + + def get_user_language_choice(self) -> str: + """Get language choice from user with validation.""" + while True: + try: + lang_id = input("[ENTER] Language ID to verify: ").strip() + + if not lang_id: + print(" [!] Please enter a valid language ID") + continue + + is_valid, error_msg = self.validate_language_id(lang_id) + + if not is_valid: + print(f" [✗] {error_msg}") + continue + + return lang_id + + except KeyboardInterrupt: + print("\n\n[EXIT] Operation cancelled by user") + sys.exit(0) + + except EOFError: + print("\n\n[EXIT] Goodbye!") + sys.exit(0) + + def run(self) -> None: + """Main application loop.""" + try: + self.display_header() + self.display_available_languages() + + while True: + lang_id = self.get_user_language_choice() + + print(f"\n[>] Verifying language file: {lang_id}.json") + print("=" * 50) + + stats = self.fix_translation_file(lang_id) + self.display_translation_results(lang_id, stats) + + except KeyboardInterrupt: + print("\n\n[EXIT] Operation cancelled by user") + sys.exit(0) + + except Exception as e: + print(f"\n[✗] Unexpected error: {e}") + sys.exit(1) + + +def main() -> None: + """Entry point of the application.""" + locale_manager = LocaleManager() + locale_manager.run() + + +if __name__ == "__main__": + main() diff --git a/src/static/locale/language_template.json b/src/static/locales/locale_template.json similarity index 100% rename from src/static/locale/language_template.json rename to src/static/locales/locale_template.json diff --git a/src/static/locale/nl-nl.json b/src/static/locales/nl-NL.json similarity index 100% rename from src/static/locale/nl-nl.json rename to src/static/locales/nl-NL.json diff --git a/src/static/locale/pl.json b/src/static/locales/pl-PL.json similarity index 100% rename from src/static/locale/pl.json rename to src/static/locales/pl-PL.json diff --git a/src/static/locale/ru.json b/src/static/locales/ru-RU.json similarity index 100% rename from src/static/locale/ru.json rename to src/static/locales/ru-RU.json diff --git a/src/static/locales/supported_locales.json b/src/static/locales/supported_locales.json new file mode 100644 index 0000000..b4c2bbf --- /dev/null +++ b/src/static/locales/supported_locales.json @@ -0,0 +1,134 @@ +[ + { + "flag": "🇺🇸", + "lang_id": "en-US", + "lang_name": "English (United States)", + "lang_name_localized": "English (United States)" + }, + { + "flag": "🇸🇦", + "lang_id": "ar-SA", + "lang_name": "Arabic (Saudi Arabia)", + "lang_name_localized": "العربية (السعودية)" + }, + { + "flag": "🇧🇾", + "lang_id": "be-BY", + "lang_name": "Belarusian (Belarus)", + "lang_name_localized": "Беларуская (Беларусь)" + }, + { + "flag": "🏴󠁥󠁳󠁣󠁴󠁿", + "lang_id": "ca-ES", + "lang_name": "Catalan (Spain)", + "lang_name_localized": "Català (Espanya)" + }, + { + "flag": "🇨🇿", + "lang_id": "cs-CZ", + "lang_name": "Czech (Czech Republic)", + "lang_name_localized": "Česky (Česká republika)" + }, + { + "flag": "🇩🇪", + "lang_id": "de-DE", + "lang_name": "German (Germany)", + "lang_name_localized": "Deutsch (Deutschland)" + }, + { + "flag": "🇪🇸", + "lang_id": "es-ES", + "lang_name": "Spanish (Spain)", + "lang_name_localized": "Español (España)" + }, + { + "flag": "🇮🇷", + "lang_id": "fa-IR", + "lang_name": "Persian (Iran)", + "lang_name_localized": "فارسی (ایران)" + }, + { + "flag": "🇨🇦", + "lang_id": "fr-CA", + "lang_name": "French (Canada)", + "lang_name_localized": "Français (Canada)" + }, + { + "flag": "🇫🇷", + "lang_id": "fr-FR", + "lang_name": "French (France)", + "lang_name_localized": "Français (France)" + }, + { + "flag": "🇮🇹", + "lang_id": "it-IT", + "lang_name": "Italian (Italy)", + "lang_name_localized": "Italiano (Italia)" + }, + { + "flag": "🇯🇵", + "lang_id": "ja-JP", + "lang_name": "Japanese (Japan)", + "lang_name_localized": "日本語 (日本)" + }, + { + "flag": "🇰🇷", + "lang_id": "ko-KR", + "lang_name": "Korean (South Korea)", + "lang_name_localized": "한국어 (대한민국)" + }, + { + "flag": "🇳🇱", + "lang_id": "nl-NL", + "lang_name": "Dutch (Netherlands)", + "lang_name_localized": "Nederlands (Nederland)" + }, + { + "flag": "🇵🇱", + "lang_id": "pl-PL", + "lang_name": "Polish (Poland)", + "lang_name_localized": "Polski (Polska)" + }, + { + "flag": "🇷🇺", + "lang_id": "ru-RU", + "lang_name": "Russian (Russia)", + "lang_name_localized": "Русский (Россия)" + }, + { + "flag": "🇸🇪", + "lang_id": "sv-SE", + "lang_name": "Swedish (Sweden)", + "lang_name_localized": "Svenska (Sverige)" + }, + { + "flag": "🇹🇭", + "lang_id": "th-TH", + "lang_name": "Thai (Thailand)", + "lang_name_localized": "ภาษาไทย (ประเทศไทย)" + }, + { + "flag": "🇹🇷", + "lang_id": "tr-TR", + "lang_name": "Turkish (Turkey)", + "lang_name_localized": "Türkçe (Türkiye)" + }, + { + "flag": "🇺🇦", + "lang_id": "uk-UA", + "lang_name": "Ukrainian (Ukraine)", + "lang_name_localized": "Українська (Україна)" + }, + { + "flag": "🇨🇳", + "lang_id": "zh-CN", + "lang_name": "Chinese (Simplified, China)", + "lang_name_localized": "中文(简体,中国)" + }, + { + "flag": "🇭🇰", + "lang_id": "zh-HK", + "lang_name": "Chinese (Traditional, Hong Kong)", + "lang_name_localized": "中文(繁體,香港)" + } +] \ No newline at end of file diff --git a/src/static/locale/sv-se.json b/src/static/locales/sv-SE.json similarity index 100% rename from src/static/locale/sv-se.json rename to src/static/locales/sv-SE.json diff --git a/src/static/locale/th.json b/src/static/locales/th-TH.json similarity index 100% rename from src/static/locale/th.json rename to src/static/locales/th-TH.json diff --git a/src/static/locale/tr-tr.json b/src/static/locales/tr-TR.json similarity index 100% rename from src/static/locale/tr-tr.json rename to src/static/locales/tr-TR.json diff --git a/src/static/locale/uk.json b/src/static/locales/uk-UA.json similarity index 100% rename from src/static/locale/uk.json rename to src/static/locales/uk-UA.json diff --git a/src/static/locale/zh-cn.json b/src/static/locales/zh-CN.json similarity index 100% rename from src/static/locale/zh-cn.json rename to src/static/locales/zh-CN.json diff --git a/src/static/locale/zh-hk.json b/src/static/locales/zh-HK.json similarity index 100% rename from src/static/locale/zh-hk.json rename to src/static/locales/zh-HK.json