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:
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:

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",
"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"
}

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