Changes to migrate to BCP 47 locale standard

This commit is contained in:
Iker García Calviño 2025-07-15 12:51:26 +02:00 committed by Donald Zou
parent 2cf337a606
commit 0599503779
27 changed files with 791 additions and 473 deletions

View File

@ -1110,9 +1110,9 @@ def API_Welcome_Finish():
class Locale: class Locale:
def __init__(self): def __init__(self):
self.localePath = './static/locale/' self.localePath = './static/locales/'
self.activeLanguages = {} 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']) self.activeLanguages = sorted(json.loads(''.join(f.readlines())), key=lambda x : x['lang_name'])
def getLanguage(self) -> dict | None: def getLanguage(self) -> dict | None:

View File

@ -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": "Українська"
}
]

View File

@ -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")

View File

@ -1,312 +1,369 @@
{ {
"Welcome to": "Bienvenido", " file": " archivo",
"Username": "Usuario", "(.*) Available IP Address": "$1 Dirección IP disponible",
"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",
"(.*) Minutes": "$1 Minutos/i", "(.*) Minutes": "$1 Minutos/i",
"Configuration Settings": "Configuración", "(.*) Seconds": "$1 Segundos/i",
"Peer Jobs": "Jobs del Peer", "(.*) Used": "",
"Active Jobs": "Jobs Activos", "(.*) is off": "$1 está desactivado",
"All Active Jobs": "Todos los Jobs Activos", "(.*) is on": "$1 está activo",
"Logs": "Logs", "([0-9].*) Backups?": "$1 Backups?",
"Private Key": "Clave privada", "([0-9].*) Peers?": "¿$1 Peers?",
"\\(Required for QR Code and Download\\)": "(Requerido para el código QR y Descarga)", "([0-9]{1,}) Interfaces": "",
"\\(Required\\)": "(Requerido)", "([0-9]{1,}) Partitions": "",
"Endpoint Allowed IPs": "Direcciones IP del endpoint permitidas", "(v[0-9.]{1,}) is now available for update!": "$1 está disponible para actualizar!",
"DNS": "DNS", "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",
"Optional Settings": "Ajustes opcionales", "2\\. Enter the TOTP generated by your authenticator to verify": "2. Inserta el código TOTP generado por tu autentificador para verificar",
"Pre-Shared Key": "Clave pre-compartida", "API Key": "Clave API",
"MTU": "MTU", "API Key created": "Clave API creada",
"Persistent Keepalive": "Keepalive persistente", "API Key deleted": "Clave API eliminada",
"Reset Data Usage": "Resetear los datos de uso", "API Keys": "Clave API",
"Total": "Total", "API Keys function is failed to disable": "La funcionalidad de clave API no se ha habilitado",
"Sent": "Enviado", "API Keys function is failed to enable": "La funcionalidad de clave API no se ha habilitado",
"Received": "Recibido", "API Keys function is successfully disabled": "La funcionalidad de clave API se ha deshabilitado con éxito",
"Revert": "Revertir", "API Keys function is successfully enabled": "La funcionalidad de clave API se ha habilitado con éxito",
"Save Peer": "Guardar el Peer", "Access Remote Server": "Acceder a servidor remoto",
"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",
"Access Restricted": "Accesso Restringido", "Access Restricted": "Accesso Restringido",
"Restrict Access": "Restringir Acceso", "Account Settings": "Ajustes de la cuenta",
"Restricting\\.\\.\\.": "Restringiendo...", "Active Jobs": "Jobs Activos",
"Allow Access": "Habilitar Acceso", "Add": "añadir",
"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",
"Add Peers": "Agregar un Peer", "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", "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.", "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?", "CPU": "",
"You can add up to (.*) peers": "Puedes añadir hasta $1 peers", "CPU Usage": "",
"Use your own Private and Public Key": "Usa tu propia clave pública y privada", "Cancel": "Cancelar",
"Enter IP Address/CIDR": "ntroduce la dirección IP/CIDR", "Checking backups...": "Comprobando backups...",
"IP Address/CIDR": "Dirección IP/CIDR", "Checking for update...": "Buscando actualizaciones...",
"or": "o", "Clear Selection": "Deseleccionar",
"Pick Available IP": "Selecciona una IP disponible", "Click": "Click",
"No available IP containing": "Ninguna IP disponible que contenga", "Click to change a backup": "click para cambiar el backup",
"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",
"Complete": "Completado", "Complete": "Completado",
"(v[0-9.]{1,}) is now available for update!": "$1 está disponible para actualizar!", "Configuration": "Configuración",
"Current Version:": "Versión actual:", "Configuration File": "",
"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 Name": "Nombre de la configuración", "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 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.", "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", "Configuration name is invalid. Possible reasons:": "El nombre de la configuración es inválido. Motivo:",
"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 restored": "Configuración restaurada", "Configuration restored": "Configuración restaurada",
"Allowed IP already taken by another peer": "IP Permitida ya ha sido seleccionada por otro peer", "Configuration saved": "Configuración guardada",
"Failed to allow access of peer (.*)": "Fallo de habilitar acceso del peer $1", "Configurations": "Configuraciones",
"Failed to save configuration through WireGuard": "Fallo de salvar configuración a través de WireGuard", "Configurations Directory": "Carpeta de configuraciones",
"Allow access successfully": "Acceso permitido con éxito", "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\\)": "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)", "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\\)": "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"
} }

View File

@ -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()

View File

@ -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": "中文(繁體,香港)"
}
]