402 lines
31 KiB
Python
402 lines
31 KiB
Python
import re
|
|
import json
|
|
|
|
trans_dict = {
|
|
"Acesso": {"en": "Access", "es": "Acceso", "fr": "Accès"},
|
|
"Adicionar": {"en": "Add", "es": "Añadir", "fr": "Ajouter"},
|
|
"Agenda de Reservas": {"en": "Booking Schedule", "es": "Agenda de Reservas", "fr": "Calendrier des Réservations"},
|
|
"Alertas por Email": {"en": "Email Alerts", "es": "Alertas por Email", "fr": "Alertes par Email"},
|
|
"Alterar": {"en": "Change", "es": "Cambiar", "fr": "Modifier"},
|
|
"Aparência": {"en": "Appearance", "es": "Apariencia", "fr": "Apparence"},
|
|
"As Minhas Quotas": {"en": "My Dues", "es": "Mis Cuotas", "fr": "Mes Cotisations"},
|
|
"Ativar Agora": {"en": "Activate Now", "es": "Activar Ahora", "fr": "Activer Maintenant"},
|
|
"Atualizar Segurança": {"en": "Update Security", "es": "Actualizar Seguridad", "fr": "Mettre à jour la Sécurité"},
|
|
"Avisos de Cobrança": {"en": "Billing Notices", "es": "Avisos de Cobro", "fr": "Avis de Facturation"},
|
|
"Ações": {"en": "Actions", "es": "Acciones", "fr": "Actions"},
|
|
"Balanço Líquido": {"en": "Net Balance", "es": "Balance Neto", "fr": "Solde Net"},
|
|
"Cancelar": {"en": "Cancel", "es": "Cancelar", "fr": "Annuler"},
|
|
"Categoria": {"en": "Category", "es": "Categoría", "fr": "Catégorie"},
|
|
"Claro": {"en": "Light", "es": "Claro", "fr": "Clair"},
|
|
"Comércio": {"en": "Commerce", "es": "Comercio", "fr": "Commerce"},
|
|
"Condómino": {"en": "Resident", "es": "Residente", "fr": "Résident"},
|
|
"Condóminos": {"en": "Residents", "es": "Residentes", "fr": "Résidents"},
|
|
"Confirmar": {"en": "Confirm", "es": "Confirmar", "fr": "Confirmer"},
|
|
"Confirmar Nova Palavra-passe": {"en": "Confirm New Password", "es": "Confirmar Nueva Contraseña", "fr": "Confirmer le Nouveau Mot de passe"},
|
|
"Confirmar Reserva": {"en": "Confirm Booking", "es": "Confirmar Reserva", "fr": "Confirmer la Réservation"},
|
|
"Confirmação": {"en": "Confirmation", "es": "Confirmación", "fr": "Confirmation"},
|
|
"Consulte as suas despesas e faturas emitidas": {"en": "Check your expenses and issued invoices", "es": "Consulte sus gastos y facturas emitidas", "fr": "Consultez vos dépenses et factures émises"},
|
|
"Contacto": {"en": "Contact", "es": "Contacto", "fr": "Contact"},
|
|
"Conversas": {"en": "Conversations", "es": "Conversaciones", "fr": "Conversations"},
|
|
"Criar Grupo": {"en": "Create Group", "es": "Crear Grupo", "fr": "Créer un Groupe"},
|
|
"Criar Novo Grupo": {"en": "Create New Group", "es": "Crear Nuevo Grupo", "fr": "Créer un Nouveau Groupe"},
|
|
"Dados Pessoais": {"en": "Personal Data", "es": "Datos Personales", "fr": "Données Personnelles"},
|
|
"Dashboard": {"en": "Dashboard", "es": "Panel de Control", "fr": "Tableau de Bord"},
|
|
"Data": {"en": "Date", "es": "Fecha", "fr": "Date"},
|
|
"Data Emissão": {"en": "Issue Date", "es": "Fecha de Emisión", "fr": "Date d'Émission"},
|
|
"Data Vencimento": {"en": "Due Date", "es": "Fecha de Vencimiento", "fr": "Date d'Échéance"},
|
|
"Descarregar Recibo": {"en": "Download Receipt", "es": "Descargar Recibo", "fr": "Télécharger le Reçu"},
|
|
"Descrição": {"en": "Description", "es": "Descripción", "fr": "Description"},
|
|
"Detalhes da Rota": {"en": "Route Details", "es": "Detalles de la Ruta", "fr": "Détails de l'Itinéraire"},
|
|
"Detalhes do movimento": {"en": "Movement details", "es": "Detalles del movimiento", "fr": "Détails du mouvement"},
|
|
"Detalhes e Navegação": {"en": "Details and Navigation", "es": "Detalles y Navegación", "fr": "Détails et Navigation"},
|
|
"Diário Financeiro": {"en": "Financial Diary", "es": "Diario Financiero", "fr": "Journal Financier"},
|
|
"Editar": {"en": "Edit", "es": "Editar", "fr": "Modifier"},
|
|
"Eliminar": {"en": "Delete", "es": "Eliminar", "fr": "Supprimer"},
|
|
"Em Dívida": {"en": "In Debt", "es": "En Deuda", "fr": "En Dette"},
|
|
"Em resolução": {"en": "In Resolution", "es": "En resolución", "fr": "En résolution"},
|
|
"Email": {"en": "Email", "es": "Correo", "fr": "Email"},
|
|
"Emita faturas ou avise condóminos individualmente": {"en": "Issue invoices or notify residents individually", "es": "Emita facturas o avise a los residentes individualmente", "fr": "Émettez des factures ou informez les résidents individuellement"},
|
|
"Emitir Fatura": {"en": "Issue Invoice", "es": "Emitir Factura", "fr": "Émettre une Facture"},
|
|
"Emitir Nova Fatura": {"en": "Issue New Invoice", "es": "Emitir Nueva Factura", "fr": "Émettre une Nouvelle Facture"},
|
|
"Endereço de email": {"en": "Email Address", "es": "Dirección de correo", "fr": "Adresse e-mail"},
|
|
"Enviar por Email": {"en": "Send by Email", "es": "Enviar por Correo", "fr": "Envoyer par Email"},
|
|
"Escuro": {"en": "Dark", "es": "Oscuro", "fr": "Sombre"},
|
|
"Espaço": {"en": "Space", "es": "Espacio", "fr": "Espace"},
|
|
"Espaços Comuns": {"en": "Common Spaces", "es": "Espacios Comunes", "fr": "Espaces Communs"},
|
|
"Estado": {"en": "Status", "es": "Estado", "fr": "Statut"},
|
|
"Estado Quotas": {"en": "Dues Status", "es": "Estado de Cuotas", "fr": "Statut des Cotisations"},
|
|
"Ex: 14:00 - 16:00": {"en": "E.g.: 14:00 - 16:00", "es": "Ej: 14:00 - 16:00", "fr": "Ex : 14:00 - 16:00"},
|
|
"Ex: 1º Esq": {"en": "E.g.: 1st Left", "es": "Ej: 1º Izq", "fr": "Ex : 1er Gauche"},
|
|
"Ex: Hall de entrada": {"en": "E.g.: Entrance Hall", "es": "Ej: Hall de entrada", "fr": "Ex : Hall d'entrée"},
|
|
"Ex: Limpeza, Elevadores, Quotas...": {"en": "E.g.: Cleaning, Elevators, Dues...", "es": "Ej: Limpieza, Ascensores, Cuotas...", "fr": "Ex : Nettoyage, Ascenseurs, Cotisations..."},
|
|
"Ex: Lâmpada fundida": {"en": "E.g.: Blown bulb", "es": "Ej: Bombilla fundida", "fr": "Ex : Ampoule grillée"},
|
|
"Explore e encontre rotas no condomínio": {"en": "Explore and find routes in the condo", "es": "Explore y encuentre rutas en el condominio", "fr": "Explorez et trouvez des itinéraires dans la copropriété"},
|
|
"Fatura": {"en": "Invoice", "es": "Factura", "fr": "Facture"},
|
|
"Faturar na Hora": {"en": "Invoice Now", "es": "Facturar al Instante", "fr": "Facturer Maintenant"},
|
|
"Faturação": {"en": "Billing", "es": "Facturación", "fr": "Facturation"},
|
|
"Fazer Reserva": {"en": "Make Booking", "es": "Hacer Reserva", "fr": "Faire une Réservation"},
|
|
"Finanças": {"en": "Finances", "es": "Finanzas", "fr": "Finances"},
|
|
"Fração": {"en": "Unit", "es": "Fracción", "fr": "Unité"},
|
|
"Fórum do Condomínio": {"en": "Condo Forum", "es": "Foro del Condominio", "fr": "Forum de la Copropriété"},
|
|
"Geral": {"en": "General", "es": "General", "fr": "Général"},
|
|
"Gestão de Condóminos": {"en": "Residents Management", "es": "Gestión de Residentes", "fr": "Gestion des Résidents"},
|
|
"Gestão de pedidos e reparações": {"en": "Requests and repairs management", "es": "Gestión de solicitudes y reparaciones", "fr": "Gestion des demandes et réparations"},
|
|
"Ginásio": {"en": "Gym", "es": "Gimnasio", "fr": "Salle de Sport"},
|
|
"Ginásio Privado": {"en": "Private Gym", "es": "Gimnasio Privado", "fr": "Salle de Sport Privée"},
|
|
"Grupo": {"en": "Group", "es": "Grupo", "fr": "Groupe"},
|
|
"Grupo partilhado": {"en": "Shared group", "es": "Grupo compartido", "fr": "Groupe partagé"},
|
|
"Guardar Alterações": {"en": "Save Changes", "es": "Guardar Cambios", "fr": "Enregistrer les Modifications"},
|
|
"Guardar Condómino": {"en": "Save Resident", "es": "Guardar Residente", "fr": "Enregistrer le Résident"},
|
|
"Histórico de Reservas": {"en": "Booking History", "es": "Historial de Reservas", "fr": "Historique des Réservations"},
|
|
"Horário": {"en": "Schedule", "es": "Horario", "fr": "Horaire"},
|
|
"Horário: 08:00 - 22:00": {"en": "Hours: 08:00 - 22:00", "es": "Horario: 08:00 - 22:00", "fr": "Horaires : 08:00 - 22:00"},
|
|
"Idioma da Aplicação": {"en": "App Language", "es": "Idioma de la Aplicación", "fr": "Langue de l'Application"},
|
|
"Lazer": {"en": "Leisure", "es": "Ocio", "fr": "Loisirs"},
|
|
"Limpar": {"en": "Clear", "es": "Limpiar", "fr": "Effacer"},
|
|
"Lista completa de agendamentos em todos os espaços de lazer": {"en": "Complete list of bookings in all leisure spaces", "es": "Lista completa de reservas en todos los espacios de ocio", "fr": "Liste complète des réservations dans tous les espaces de loisirs"},
|
|
"Localização": {"en": "Location", "es": "Ubicación", "fr": "Emplacement"},
|
|
"Manutenção": {"en": "Maintenance", "es": "Mantenimiento", "fr": "Maintenance"},
|
|
"Manutenção e Ocorrências": {"en": "Maintenance and Issues", "es": "Mantenimiento e Incidencias", "fr": "Maintenance et Incidents"},
|
|
"Manutenções Ativas": {"en": "Active Maintenance", "es": "Mantenimientos Activos", "fr": "Maintenances Actives"},
|
|
"Mapa": {"en": "Map", "es": "Mapa", "fr": "Carte"},
|
|
"Marcar como lida": {"en": "Mark as read", "es": "Marcar como leída", "fr": "Marquer comme lu"},
|
|
"Mensagens": {"en": "Messages", "es": "Mensajes", "fr": "Messages"},
|
|
"Meu Perfil": {"en": "My Profile", "es": "Mi Perfil", "fr": "Mon Profil"},
|
|
"Min. 8 caracteres": {"en": "Min. 8 characters", "es": "Mín. 8 caracteres", "fr": "Min. 8 caractères"},
|
|
"Minhas Contas": {"en": "My Accounts", "es": "Mis Cuentas", "fr": "Mes Comptes"},
|
|
"Minhas Faturas": {"en": "My Invoices", "es": "Mis Facturas", "fr": "Mes Factures"},
|
|
"Morador": {"en": "Resident", "es": "Residente", "fr": "Résident"},
|
|
"Mudar Permissões": {"en": "Change Permissions", "es": "Cambiar Permisos", "fr": "Modifier les Permissions"},
|
|
"MyCondominium": {"en": "MyCondominium", "es": "MyCondominium", "fr": "MyCondominium"},
|
|
"Navegação Inteligente": {"en": "Smart Navigation", "es": "Navegación Inteligente", "fr": "Navigation Intelligente"},
|
|
"Nome Completo": {"en": "Full Name", "es": "Nombre Completo", "fr": "Nom Complet"},
|
|
"Nome do Grupo": {"en": "Group Name", "es": "Nombre del Grupo", "fr": "Nom du Groupe"},
|
|
"Nome do proprietário": {"en": "Owner Name", "es": "Nombre del propietario", "fr": "Nom du propriétaire"},
|
|
"Nome do residente": {"en": "Resident Name", "es": "Nombre del residente", "fr": "Nom du résident"},
|
|
"Notificar": {"en": "Notify", "es": "Notificar", "fr": "Notifier"},
|
|
"Notificações": {"en": "Notifications", "es": "Notificaciones", "fr": "Notifications"},
|
|
"Notificações Push no Navegador": {"en": "Browser Push Notifications", "es": "Notificaciones Push en el Navegador", "fr": "Notifications Push du Navigateur"},
|
|
"Nova Palavra-passe": {"en": "New Password", "es": "Nueva Contraseña", "fr": "Nouveau Mot de passe"},
|
|
"Nova Reserva": {"en": "New Booking", "es": "Nueva Reserva", "fr": "Nouvelle Réservation"},
|
|
"Novo Registo": {"en": "New Registration", "es": "Nuevo Registro", "fr": "Nouvel Enregistrement"},
|
|
"Nível de Acesso": {"en": "Access Level", "es": "Nivel de Acceso", "fr": "Niveau d'Accès"},
|
|
"Pagamentos": {"en": "Payments", "es": "Pagos", "fr": "Paiements"},
|
|
"Pagamentos Concluídos": {"en": "Completed Payments", "es": "Pagos Completados", "fr": "Paiements Terminés"},
|
|
"Pagar": {"en": "Pay", "es": "Pagar", "fr": "Payer"},
|
|
"Pago": {"en": "Paid", "es": "Pagado", "fr": "Payé"},
|
|
"Palavra-passe": {"en": "Password", "es": "Contraseña", "fr": "Mot de passe"},
|
|
"Palavra-passe Atual": {"en": "Current Password", "es": "Contraseña Actual", "fr": "Mot de passe Actuel"},
|
|
"Parque Jogos": {"en": "Playground", "es": "Parque de Juegos", "fr": "Aire de Jeux"},
|
|
"Parque de Jogos": {"en": "Playground", "es": "Parque de Juegos", "fr": "Aire de Jeux"},
|
|
"Permissões": {"en": "Permissions", "es": "Permisos", "fr": "Permissions"},
|
|
"Portal de Gestão": {"en": "Management Portal", "es": "Portal de Gestión", "fr": "Portail de Gestion"},
|
|
"Preferências": {"en": "Preferences", "es": "Preferencias", "fr": "Préférences"},
|
|
"Preferências da Aplicação": {"en": "App Preferences", "es": "Preferencias de la Aplicación", "fr": "Préférences de l'Application"},
|
|
"Prioridade": {"en": "Priority", "es": "Prioridad", "fr": "Priorité"},
|
|
"Proprietário": {"en": "Owner", "es": "Propietario", "fr": "Propriétaire"},
|
|
"Próximas Reservas": {"en": "Upcoming Bookings", "es": "Próximas Reservas", "fr": "Prochaines Réservations"},
|
|
"Quadro de Avisos": {"en": "Notice Board", "es": "Tablón de Anuncios", "fr": "Tableau d'Affichage"},
|
|
"Quotas em Atraso": {"en": "Overdue Dues", "es": "Cuotas Atrasadas", "fr": "Cotisations en Retard"},
|
|
"RIO TEJO": {"en": "RIO TEJO", "es": "RIO TEJO", "fr": "RIO TEJO"},
|
|
"Recarregar Página": {"en": "Reload Page", "es": "Recargar Página", "fr": "Recharger la Page"},
|
|
"Recibo": {"en": "Receipt", "es": "Recibo", "fr": "Reçu"},
|
|
"Registar Movimento": {"en": "Register Movement", "es": "Registrar Movimiento", "fr": "Enregistrer le Mouvement"},
|
|
"Registar Movimento Financeiro": {"en": "Register Financial Movement", "es": "Registrar Movimiento Financiero", "fr": "Enregistrer le Mouvement Financier"},
|
|
"Relatórios Semanais Automáticos": {"en": "Automated Weekly Reports", "es": "Informes Semanales Automáticos", "fr": "Rapports Hebdomadaires Automatiques"},
|
|
"Reportar": {"en": "Report", "es": "Reportar", "fr": "Signaler"},
|
|
"Reportar Ocorrência": {"en": "Report Issue", "es": "Reportar Incidencia", "fr": "Signaler un Incident"},
|
|
"Reportar Problema": {"en": "Report Problem", "es": "Reportar Problema", "fr": "Signaler un Problème"},
|
|
"Reservado para (Condómino)": {"en": "Booked for (Resident)", "es": "Reservado para (Residente)", "fr": "Réservé pour (Résident)"},
|
|
"Reservar Agora": {"en": "Book Now", "es": "Reservar Ahora", "fr": "Réserver Maintenant"},
|
|
"Reservas (Mês)": {"en": "Bookings (Month)", "es": "Reservas (Mes)", "fr": "Réservations (Mois)"},
|
|
"Residencial": {"en": "Residential", "es": "Residencial", "fr": "Résidentiel"},
|
|
"Resolver": {"en": "Resolve", "es": "Resolver", "fr": "Résoudre"},
|
|
"Resolver Problemas": {"en": "Solve Problems", "es": "Resolver Problemas", "fr": "Résoudre des Problèmes"},
|
|
"Restaurar Base de Dados": {"en": "Restore Database", "es": "Restaurar Base de Datos", "fr": "Restaurer la Base de Données"},
|
|
"Saldo Disponível": {"en": "Available Balance", "es": "Saldo Disponible", "fr": "Solde Disponible"},
|
|
"Salão de Festas": {"en": "Party Room", "es": "Salón de Fiestas", "fr": "Salle de Fêtes"},
|
|
"Segurança": {"en": "Security", "es": "Seguridad", "fr": "Sécurité"},
|
|
"Selecionar Moradores": {"en": "Select Residents", "es": "Seleccionar Residentes", "fr": "Sélectionner des Résidents"},
|
|
"Sem novas notificações": {"en": "No new notifications", "es": "No hay nuevas notificaciones", "fr": "Aucune nouvelle notification"},
|
|
"Sem reservas": {"en": "No bookings", "es": "Sin reservas", "fr": "Aucune réservation"},
|
|
"Sem valores pendentes": {"en": "No pending values", "es": "Sin valores pendientes", "fr": "Aucune valeur en attente"},
|
|
"Senha de acesso": {"en": "Access Password", "es": "Contraseña de acceso", "fr": "Mot de passe d'accès"},
|
|
"Serviços": {"en": "Services", "es": "Servicios", "fr": "Services"},
|
|
"Sistema": {"en": "System", "es": "Sistema", "fr": "Système"},
|
|
"Telefone": {"en": "Phone", "es": "Teléfono", "fr": "Téléphone"},
|
|
"Telemóvel": {"en": "Mobile", "es": "Móvil", "fr": "Portable"},
|
|
"Terminar Sessão": {"en": "Log Out", "es": "Cerrar Sesión", "fr": "Se Déconnecter"},
|
|
"Tipo": {"en": "Type", "es": "Tipo", "fr": "Type"},
|
|
"Total Pago": {"en": "Total Paid", "es": "Total Pagado", "fr": "Total Payé"},
|
|
"Total Pendente": {"en": "Total Pending", "es": "Total Pendiente", "fr": "Total en Attente"},
|
|
"Total agendado": {"en": "Total Scheduled", "es": "Total Agendado", "fr": "Total Programmé"},
|
|
"Título do Problema": {"en": "Problem Title", "es": "Título del Problema", "fr": "Titre du Problème"},
|
|
"VIA CENTRAL": {"en": "VIA CENTRAL", "es": "VIA CENTRAL", "fr": "VIA CENTRAL"},
|
|
"Valor": {"en": "Amount", "es": "Valor", "fr": "Montant"},
|
|
"Valor (€)": {"en": "Amount (€)", "es": "Valor (€)", "fr": "Montant (€)"},
|
|
"Valor Pendente (€)": {"en": "Pending Amount (€)", "es": "Valor Pendiente (€)", "fr": "Montant en Attente (€)"},
|
|
"Vencimento": {"en": "Due Date", "es": "Vencimiento", "fr": "Échéance"},
|
|
"Ver todas as Reservas": {"en": "See all Bookings", "es": "Ver todas las Reservas", "fr": "Voir toutes les Réservations"},
|
|
"Em Progresso": {"en": "In Progress", "es": "En Progreso", "fr": "En Cours"},
|
|
"Receita": {"en": "Income", "es": "Ingreso", "fr": "Revenu"},
|
|
"Despesa": {"en": "Expense", "es": "Gasto", "fr": "Dépense"},
|
|
"Em Validação": {"en": "Validating", "es": "En Validación", "fr": "En Validation"},
|
|
"Alta": {"en": "High", "es": "Alta", "fr": "Élevée"},
|
|
"Baixa": {"en": "Low", "es": "Baja", "fr": "Basse"},
|
|
"Novo": {"en": "New", "es": "Nuevo", "fr": "Nouveau"},
|
|
"Resolvido": {"en": "Resolved", "es": "Resuelto", "fr": "Résolu"},
|
|
"Pendente": {"en": "Pending", "es": "Pendiente", "fr": "En Attente"},
|
|
"Confirmado": {"en": "Confirmed", "es": "Confirmado", "fr": "Confirmé"},
|
|
"Média": {"en": "Medium", "es": "Media", "fr": "Moyenne"},
|
|
"Atrasado": {"en": "Overdue", "es": "Atrasado", "fr": "En Retard"},
|
|
"Ana Silva": {"en": "Ana Silva", "es": "Ana Silva", "fr": "Ana Silva"},
|
|
"Carlos Santos": {"en": "Carlos Santos", "es": "Carlos Santos", "fr": "Carlos Santos"},
|
|
"Maria Pereira": {"en": "Maria Pereira", "es": "Maria Pereira", "fr": "Maria Pereira"},
|
|
"João Ferreira": {"en": "João Ferreira", "es": "João Ferreira", "fr": "João Ferreira"},
|
|
"Sofia Costa": {"en": "Sofia Costa", "es": "Sofia Costa", "fr": "Sofia Costa"},
|
|
"Administração": {"en": "Administration", "es": "Administración", "fr": "Administration"},
|
|
"Utilizador": {"en": "User", "es": "Usuario", "fr": "Utilisateur"},
|
|
"Pesquisar condómino por nome ou fração...": {"en": "Search resident by name or unit...", "es": "Buscar residente por nombre o fracción...", "fr": "Rechercher un résident par nom ou unité..."},
|
|
"Pesquisar transação...": {"en": "Search transaction...", "es": "Buscar transacción...", "fr": "Rechercher una transacción..."},
|
|
"Pesquisar na lista...": {"en": "Search in list...", "es": "Buscar en la lista...", "fr": "Rechercher dans la liste..."},
|
|
"Pesquisar fatura...": {"en": "Search invoice...", "es": "Buscar factura...", "fr": "Rechercher une facture..."},
|
|
"Escreva a sua mensagem...": {"en": "Write your message...", "es": "Escriba su mensaje...", "fr": "Écrivez votre message..."},
|
|
"Pesquisar reserva...": {"en": "Search booking...", "es": "Buscar reserva...", "fr": "Rechercher une réservation..."},
|
|
"vs. mês passado": {"en": "vs. last month", "es": "vs. mes pasado", "fr": "vs. mois dernier"},
|
|
"Algo correu mal (Erro na Aplicação)": {"en": "Something went wrong (App Error)", "es": "Algo salió mal (Error de Aplicación)", "fr": "Un problème est survenu (Erreur d'Application)"},
|
|
"Email ou Palavra-passe incorreta": {"en": "Incorrect Email or Password", "es": "Correo o Contraseña incorrecta", "fr": "E-mail ou Mot de passe incorrect"},
|
|
"A entrar...": {"en": "Logging in...", "es": "Entrando...", "fr": "Connexion en cours..."},
|
|
"Entrar": {"en": "Log In", "es": "Entrar", "fr": "Se Connecter"},
|
|
"Nenhuma ocorrência encontrada.": {"en": "No issues found.", "es": "Ninguna incidencia encontrada.", "fr": "Aucun incident trouvé."},
|
|
"Nenhuma transação financeira encontrada.": {"en": "No financial transactions found.", "es": "Ninguna transacción financiera encontrada.", "fr": "Aucune transaction financière trouvée."},
|
|
"Nenhum condómino encontrado.": {"en": "No resident found.", "es": "Ningún residente encontrado.", "fr": "Aucun résident trouvé."},
|
|
"Nenhuma fatura encontrada.": {"en": "No invoice found.", "es": "Ninguna factura encontrada.", "fr": "Aucune facture trouvée."},
|
|
"Nenhuma reserva encontrada.": {"en": "No booking found.", "es": "Ninguna reserva encontrada.", "fr": "Aucune réservation trouvée."},
|
|
"Tem a certeza que deseja terminar sessão?": {"en": "Are you sure you want to log out?", "es": "¿Está seguro que desea cerrar sesión?", "fr": "Êtes-vous sûr de vouloir vous déconnecter ?"},
|
|
"Permissões de utilizador atualizadas": {"en": "User permissions updated", "es": "Permisos de usuario actualizados", "fr": "Permissions utilisateur mises à jour"},
|
|
"Erro ao atualizar permissão.": {"en": "Error updating permission.", "es": "Error al actualizar permiso.", "fr": "Erreur lors de la mise à jour de la permission."},
|
|
"Morador atualizado com sucesso": {"en": "Resident updated successfully", "es": "Residente actualizado con éxito", "fr": "Résident mis à jour avec succès"},
|
|
"Morador adicionado com sucesso": {"en": "Resident added successfully", "es": "Residente añadido con éxito", "fr": "Résident ajouté avec succès"},
|
|
"Erro ao guardar morador": {"en": "Error saving resident", "es": "Error al guardar residente", "fr": "Erreur lors de l'enregistrement du résident"},
|
|
"Ocorrência resolvida com sucesso": {"en": "Issue resolved successfully", "es": "Incidencia resuelta con éxito", "fr": "Incident résolu avec succès"},
|
|
"Ocorrência atualizada": {"en": "Issue updated", "es": "Incidencia actualizada", "fr": "Incident mis à jour"},
|
|
"Transação adicionada com sucesso": {"en": "Transaction added successfully", "es": "Transacción añadida con éxito", "fr": "Transaction ajoutée avec succès"},
|
|
"Erro ao guardar transação": {"en": "Error saving transaction", "es": "Error al guardar transacción", "fr": "Erreur lors de l'enregistrement de la transaction"},
|
|
"Fatura emitida com sucesso": {"en": "Invoice issued successfully", "es": "Factura emitida con éxito", "fr": "Facture émise avec succès"},
|
|
"Erro ao emitir fatura": {"en": "Error issuing invoice", "es": "Error al emitir factura", "fr": "Erreur lors de l'émission de la facture"},
|
|
"Fatura atualizada para Paga": {"en": "Invoice updated to Paid", "es": "Factura actualizada a Pagada", "fr": "Facture mise à jour sur Payée"},
|
|
"Erro ao atualizar fatura": {"en": "Error updating invoice", "es": "Error al actualizar factura", "fr": "Erreur lors de la mise à jour de la facture"},
|
|
"Reserva efetuada com sucesso": {"en": "Booking made successfully", "es": "Reserva efectuada con éxito", "fr": "Réservation effectuée avec succès"},
|
|
"Reserva atualizada com sucesso": {"en": "Booking updated successfully", "es": "Reserva actualizada con éxito", "fr": "Réservation mise à jour avec succès"},
|
|
"Erro ao fazer reserva": {"en": "Error making booking", "es": "Error al hacer reserva", "fr": "Erreur lors de la réservation"}
|
|
}
|
|
|
|
def make_key(s):
|
|
return re.sub(r'[^a-zA-Z0-9]+', '_', s.lower()).strip('_')
|
|
|
|
translations = {'pt': {}, 'en': {}, 'es': {}, 'fr': {}}
|
|
for pt_str, translations_langs in trans_dict.items():
|
|
key = make_key(pt_str)
|
|
translations['pt'][key] = pt_str
|
|
translations['en'][key] = translations_langs.get('en', pt_str)
|
|
translations['es'][key] = translations_langs.get('es', pt_str)
|
|
translations['fr'][key] = translations_langs.get('fr', pt_str)
|
|
|
|
js_dict_str = "const translations = " + json.dumps(translations, indent=4, ensure_ascii=False) + ";"
|
|
|
|
context_block = """
|
|
const LanguageContext = React.createContext();
|
|
|
|
const LanguageProvider = ({ children }) => {
|
|
const [language, setLanguage] = useState(() => {
|
|
const cookieMatch = document.cookie.match(/googtrans=\\/pt\\/([a-z]{2})/);
|
|
if (cookieMatch) return cookieMatch[1];
|
|
return 'pt';
|
|
});
|
|
|
|
const t = (key) => {
|
|
return translations[language]?.[key] || translations['pt']?.[key] || key;
|
|
};
|
|
|
|
const changeLanguage = (lang) => {
|
|
setLanguage(lang);
|
|
document.cookie = `googtrans=/pt/${lang}; path=/;`;
|
|
const domain = window.location.hostname;
|
|
if(domain !== 'localhost' && domain !== '127.0.0.1' && domain !== '') {
|
|
document.cookie = `googtrans=/pt/${lang}; domain=${domain}; path=/;`;
|
|
document.cookie = `googtrans=/pt/${lang}; domain=.${domain}; path=/;`;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<LanguageContext.Provider value={{ language, changeLanguage, t }}>
|
|
{children}
|
|
</LanguageContext.Provider>
|
|
);
|
|
};
|
|
|
|
const useTranslation = () => React.useContext(LanguageContext);
|
|
"""
|
|
|
|
with open('index.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# 1. Remove Google Translate stuff
|
|
content = re.sub(r'<!-- Google Translate Script -->.*?</script>', '', content, flags=re.DOTALL)
|
|
content = re.sub(r'<script type="text/javascript" src="https://translate\.google\.com/translate_a/element\.js\?cb=googleTranslateElementInit"></script>', '', content)
|
|
content = re.sub(r'<style>\s*/\* Esconder a barra e o widget do Google Translate nativos \*/.*?</style>', '', content, flags=re.DOTALL)
|
|
content = re.sub(r'<!-- Patch for React to not crash when Google Translate modifies the DOM -->.*?</script>', '', content, flags=re.DOTALL)
|
|
content = re.sub(r'<div id="google_translate_element"></div>', '', content)
|
|
content = content.replace('className="grid grid-cols-2 md:grid-cols-4 gap-4 notranslate"', 'className="grid grid-cols-2 md:grid-cols-4 gap-4"')
|
|
|
|
# Extract JSX part inside <script type="text/babel" data-type="module">
|
|
import_end = content.find('class ErrorBoundary')
|
|
head_content = content[:import_end]
|
|
jsx_content = content[import_end:]
|
|
|
|
# Insert LanguageContext and translations
|
|
head_content = head_content + js_dict_str + '\n' + context_block + '\n'
|
|
|
|
# 2. Add useTranslation to functional components
|
|
components_to_hook = ['Modal', 'InputGroup', 'SidebarItem', 'Card', 'LoginView', 'App', 'Badge']
|
|
for comp in components_to_hook:
|
|
pattern = rf"(const {comp} = \(.*?\) => {{\n|function {comp}\(.*\) {{\n)"
|
|
match = re.search(pattern, jsx_content)
|
|
if match:
|
|
insertion = " const { t, language, changeLanguage } = useTranslation();\n"
|
|
jsx_content = jsx_content[:match.end()] + insertion + jsx_content[match.end():]
|
|
elif comp == 'InputGroup' or comp == 'SidebarItem' or comp == 'Card':
|
|
# Arrow functions without curly braces: `const Comp = () => (...)`
|
|
pattern2 = rf"(const {comp} = \(.*?\) => )(\(.*?\);)"
|
|
match2 = re.search(pattern2, jsx_content, flags=re.DOTALL)
|
|
if match2:
|
|
jsx_content = jsx_content[:match2.start()] + match2.group(1) + "{\n const { t } = useTranslation();\n return " + match2.group(2) + "\n };\n" + jsx_content[match2.end():]
|
|
|
|
# Badge special case for translating status inside
|
|
badge_patch_old = """
|
|
return (
|
|
<span className={`px-2.5 py-1 rounded-full text-xs font-semibold border ${styles[status] || 'bg-gray-100 text-gray-700 border-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700'}`}>
|
|
{status}
|
|
</span>
|
|
);
|
|
"""
|
|
badge_patch_new = """
|
|
const translateStatus = (s) => {
|
|
if (!s) return s;
|
|
const key = s.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
return t(key);
|
|
};
|
|
|
|
return (
|
|
<span className={`px-2.5 py-1 rounded-full text-xs font-semibold border ${styles[status] || 'bg-gray-100 text-gray-700 border-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700'}`}>
|
|
{translateStatus(status)}
|
|
</span>
|
|
);
|
|
"""
|
|
if badge_patch_old in jsx_content:
|
|
jsx_content = jsx_content.replace(badge_patch_old, badge_patch_new)
|
|
|
|
# 3. Modify language selector to use changeLanguage
|
|
old_onclick = """onClick={() => {
|
|
if(lang.code === 'pt') {
|
|
document.cookie = `googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
const domain = window.location.hostname;
|
|
document.cookie = `googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=${domain}; path=/;`;
|
|
document.cookie = `googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=.${domain}; path=/;`;
|
|
} else {
|
|
document.cookie = `googtrans=/pt/${lang.code}; path=/;`;
|
|
const domain = window.location.hostname;
|
|
if(domain !== 'localhost' && domain !== '127.0.0.1') {
|
|
document.cookie = `googtrans=/pt/${lang.code}; domain=${domain}; path=/;`;
|
|
document.cookie = `googtrans=/pt/${lang.code}; domain=.${domain}; path=/;`;
|
|
}
|
|
}
|
|
window.location.reload();
|
|
}}"""
|
|
new_onclick = "onClick={() => changeLanguage(lang.code)}"
|
|
jsx_content = jsx_content.replace(old_onclick, new_onclick)
|
|
jsx_content = jsx_content.replace("const activeLang = match ? match[1] : 'pt';", "const activeLang = language;")
|
|
|
|
# 4. Wrap App with LanguageProvider
|
|
render_match = re.search(r'createRoot\(document\.getElementById\(\'root\'\)\)\.render\(\s*<App />\s*\);', jsx_content)
|
|
if render_match:
|
|
jsx_content = jsx_content.replace(render_match.group(0), "createRoot(document.getElementById('root')).render(<LanguageProvider><App /></LanguageProvider>);")
|
|
|
|
# 5. String replacement targeting only JSX area
|
|
replacements = []
|
|
for pt_str in trans_dict.keys():
|
|
key = make_key(pt_str)
|
|
# Replace >Text< with >{t('key')}<
|
|
replacements.append((f">{pt_str}<", f">{{t('{key}')}}<"))
|
|
# Replace placeholder="Text" with placeholder={t('key')}
|
|
replacements.append((f'placeholder="{pt_str}"', f'placeholder={{t(\'{key}\')}}'))
|
|
# Replace label="Text" with label={t('key')}
|
|
replacements.append((f'label="{pt_str}"', f'label={{t(\'{key}\')}}'))
|
|
# Replace title="Text" with title={t('key')}
|
|
replacements.append((f'title="{pt_str}"', f'title={{t(\'{key}\')}}'))
|
|
|
|
# Replace strings in JS code if they are completely safe (exact string constants)
|
|
# Like 'Dashboard' => t('dashboard')
|
|
replacements.append((f"'{pt_str}'", f"t('{key}')"))
|
|
replacements.append((f'"{pt_str}"', f"t('{key}')"))
|
|
|
|
new_lines = []
|
|
for line in jsx_content.split('\n'):
|
|
# Skip ErrorBoundary definition
|
|
if 'class ErrorBoundary' in line or 'Algo correu mal' in line:
|
|
new_lines.append(line)
|
|
continue
|
|
|
|
for old, new in replacements:
|
|
if old in line:
|
|
if old.startswith('>'):
|
|
line = line.replace(old, new)
|
|
elif old.startswith('placeholder=') or old.startswith('label=') or old.startswith('title='):
|
|
line = line.replace(old, new)
|
|
else:
|
|
if 'import' not in line and ':' + old not in line.replace(' ', ''):
|
|
line = re.sub(rf"(?<![a-zA-Z0-9_]){re.escape(old)}(?![a-zA-Z0-9_])", new, line)
|
|
new_lines.append(line)
|
|
|
|
jsx_content = '\n'.join(new_lines)
|
|
jsx_content = jsx_content.replace("t('pt')", "'pt'")
|
|
jsx_content = jsx_content.replace("t('en')", "'en'")
|
|
jsx_content = jsx_content.replace("t('es')", "'es'")
|
|
jsx_content = jsx_content.replace("t('fr')", "'fr'")
|
|
jsx_content = jsx_content.replace("language === t('pt')", "language === 'pt'")
|
|
|
|
with open('index.html', 'w', encoding='utf-8') as f:
|
|
f.write(head_content + jsx_content)
|
|
|
|
print("Safely applied translations.")
|