linguagem da aplicação

This commit is contained in:
2026-06-11 10:31:07 +01:00
parent 194c46ba32
commit ff481bbcd5
7 changed files with 5989 additions and 247 deletions

401
apply_i18n.py Normal file
View File

@@ -0,0 +1,401 @@
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.")

36
extract_strings.py Normal file
View File

@@ -0,0 +1,36 @@
import re
import json
def main():
with open('index.html', 'r', encoding='utf-8') as f:
content = f.read()
# 1. Extract JSX text: >Text<
jsx_texts = set(re.findall(r'>\s*([A-ZÁÉÍÓÚÀÂÊÔÃÕÇ][^<]*[a-zA-Z0-9áéíóúàâêôãõç])\s*<', content))
# 2. Extract specific attributes: placeholder="Text", title="Text", label="Text"
attrs = set()
for attr in ['placeholder', 'title', 'label']:
attrs.update(re.findall(rf'{attr}=["\']([^"\']+)["\']', content))
# 3. Extract some specific text patterns that might be missed
# E.g., strings in JSX logic: {status === 'Pago' ... }
# Or in JS alerts: confirm('Tem a certeza...')
all_strings = sorted(list(jsx_texts | attrs))
# Filter out obvious non-translatable strings like URLs, single chars, pure numbers
translatable = []
for s in all_strings:
if len(s) < 3: continue
if re.match(r'^[0-9\W]+$', s): continue
if 'http' in s or '.com' in s or '://' in s: continue
# Ignore things that look like React variables {foo}
if re.match(r'\{[a-zA-Z0-9_]+\}', s): continue
translatable.append(s)
with open('to_translate.json', 'w', encoding='utf-8') as f:
json.dump(translatable, f, indent=2, ensure_ascii=False)
if __name__ == '__main__':
main()

1193
extracted_strings.txt Normal file

File diff suppressed because it is too large Load Diff

1413
index.html

File diff suppressed because it is too large Load Diff

2971
index.html.bak Normal file

File diff suppressed because it is too large Load Diff

168
to_translate.json Normal file
View File

@@ -0,0 +1,168 @@
[
"Acesso",
"Adicionar",
"Agenda de Reservas",
"Alertas por Email",
"Alterar",
"Aparência",
"As Minhas Quotas",
"Ativar Agora",
"Atualizar Segurança",
"Avisos de Cobrança",
"Ações",
"Balanço Líquido",
"Cancelar",
"Categoria",
"Claro",
"Comércio",
"Condómino",
"Condóminos",
"Confirmar",
"Confirmar Nova Palavra-passe",
"Confirmar Reserva",
"Confirmação",
"Consulte as suas despesas e faturas emitidas",
"Contacto",
"Conversas",
"Criar Grupo",
"Criar Novo Grupo",
"Dados Pessoais",
"Dashboard",
"Data",
"Data Emissão",
"Data Vencimento",
"Descarregar Recibo",
"Descrição",
"Detalhes da Rota",
"Detalhes do movimento",
"Detalhes e Navegação",
"Diário Financeiro",
"Editar",
"Eliminar",
"Em Dívida",
"Em resolução",
"Email",
"Emita faturas ou avise condóminos individualmente",
"Emitir Fatura",
"Emitir Nova Fatura",
"Endereço de email",
"Enviar por Email",
"Escuro",
"Espaço",
"Espaços Comuns",
"Estado",
"Estado Quotas",
"Ex: 14:00 - 16:00",
"Ex: 1º Esq",
"Ex: Hall de entrada",
"Ex: Limpeza, Elevadores, Quotas...",
"Ex: Lâmpada fundida",
"Explore e encontre rotas no condomínio",
"Fatura",
"Faturar na Hora",
"Faturação",
"Fazer Reserva",
"Finanças",
"Fração",
"Fórum do Condomínio",
"Geral",
"Gestão de Condóminos",
"Gestão de pedidos e reparações",
"Ginásio",
"Ginásio Privado",
"Grupo",
"Grupo partilhado",
"Guardar Alterações",
"Guardar Condómino",
"Histórico de Reservas",
"Horário",
"Horário: 08:00 - 22:00",
"Idioma da Aplicação",
"Lazer",
"Limpar",
"Lista completa de agendamentos em todos os espaços de lazer",
"Localização",
"Manutenção",
"Manutenção e Ocorrências",
"Manutenções Ativas",
"Mapa",
"Marcar como lida",
"Mensagens",
"Meu Perfil",
"Min. 8 caracteres",
"Minhas Contas",
"Minhas Faturas",
"Morador",
"Mudar Permissões",
"MyCondominium",
"Navegação Inteligente",
"Nome Completo",
"Nome do Grupo",
"Nome do proprietário",
"Nome do residente",
"Notificar",
"Notificações",
"Notificações Push no Navegador",
"Nova Palavra-passe",
"Nova Reserva",
"Novo Registo",
"Nível de Acesso",
"Pagamentos",
"Pagamentos Concluídos",
"Pagar",
"Pago",
"Palavra-passe",
"Palavra-passe Atual",
"Parque Jogos",
"Parque de Jogos",
"Permissões",
"Portal de Gestão",
"Preferências",
"Preferências da Aplicação",
"Prioridade",
"Proprietário",
"Próximas Reservas",
"Quadro de Avisos",
"Quotas em Atraso",
"RIO TEJO",
"Recarregar Página",
"Recibo",
"Registar Movimento",
"Registar Movimento Financeiro",
"Relatórios Semanais Automáticos",
"Reportar",
"Reportar Ocorrência",
"Reportar Problema",
"Reservado para (Condómino)",
"Reservar Agora",
"Reservas (Mês)",
"Residencial",
"Resolver",
"Resolver Problemas",
"Restaurar Base de Dados",
"Saldo Disponível",
"Salão de Festas",
"Segurança",
"Selecionar Moradores",
"Sem novas notificações",
"Sem reservas",
"Sem valores pendentes",
"Senha de acesso",
"Serviços",
"Sistema",
"Telefone",
"Telemóvel",
"Terminar Sessão",
"Tipo",
"Total Pago",
"Total Pendente",
"Total agendado",
"Total: {residents.length} frações registadas",
"Título do Problema",
"VIA CENTRAL",
"Valor",
"Valor (€)",
"Valor Pendente (€)",
"Vencimento",
"Ver todas as Reservas"
]

54
translations.py Normal file
View File

@@ -0,0 +1,54 @@
import json
strings = [
"Acesso", "Adicionar", "Agenda de Reservas", "Alertas por Email", "Alterar", "Aparência",
"As Minhas Quotas", "Ativar Agora", "Atualizar Segurança", "Avisos de Cobrança", "Ações",
"Balanço Líquido", "Cancelar", "Categoria", "Claro", "Comércio", "Condómino", "Condóminos",
"Confirmar", "Confirmar Nova Palavra-passe", "Confirmar Reserva", "Confirmação",
"Consulte as suas despesas e faturas emitidas", "Contacto", "Conversas", "Criar Grupo",
"Criar Novo Grupo", "Dados Pessoais", "Dashboard", "Data", "Data Emissão", "Data Vencimento",
"Descarregar Recibo", "Descrição", "Detalhes da Rota", "Detalhes do movimento",
"Detalhes e Navegação", "Diário Financeiro", "Editar", "Eliminar", "Em Dívida", "Em resolução",
"Email", "Emita faturas ou avise condóminos individualmente", "Emitir Fatura",
"Emitir Nova Fatura", "Endereço de email", "Enviar por Email", "Escuro", "Espaço",
"Espaços Comuns", "Estado", "Estado Quotas", "Ex: 14:00 - 16:00", "Ex: 1º Esq",
"Ex: Hall de entrada", "Ex: Limpeza, Elevadores, Quotas...", "Ex: Lâmpada fundida",
"Explore e encontre rotas no condomínio", "Fatura", "Faturar na Hora", "Faturação",
"Fazer Reserva", "Finanças", "Fração", "Fórum do Condomínio", "Geral", "Gestão de Condóminos",
"Gestão de pedidos e reparações", "Ginásio", "Ginásio Privado", "Grupo", "Grupo partilhado",
"Guardar Alterações", "Guardar Condómino", "Histórico de Reservas", "Horário",
"Horário: 08:00 - 22:00", "Idioma da Aplicação", "Lazer", "Limpar",
"Lista completa de agendamentos em todos os espaços de lazer", "Localização", "Manutenção",
"Manutenção e Ocorrências", "Manutenções Ativas", "Mapa", "Marcar como lida", "Mensagens",
"Meu Perfil", "Min. 8 caracteres", "Minhas Contas", "Minhas Faturas", "Morador",
"Mudar Permissões", "MyCondominium", "Navegação Inteligente", "Nome Completo", "Nome do Grupo",
"Nome do proprietário", "Nome do residente", "Notificar", "Notificações",
"Notificações Push no Navegador", "Nova Palavra-passe", "Nova Reserva", "Novo Registo",
"Nível de Acesso", "Pagamentos", "Pagamentos Concluídos", "Pagar", "Pago", "Palavra-passe",
"Palavra-passe Atual", "Parque Jogos", "Parque de Jogos", "Permissões", "Portal de Gestão",
"Preferências", "Preferências da Aplicação", "Prioridade", "Proprietário", "Próximas Reservas",
"Quadro de Avisos", "Quotas em Atraso", "RIO TEJO", "Recarregar Página", "Recibo",
"Registar Movimento", "Registar Movimento Financeiro", "Relatórios Semanais Automáticos",
"Reportar", "Reportar Ocorrência", "Reportar Problema", "Reservado para (Condómino)",
"Reservar Agora", "Reservas (Mês)", "Residencial", "Resolver", "Resolver Problemas",
"Restaurar Base de Dados", "Saldo Disponível", "Salão de Festas", "Segurança",
"Selecionar Moradores", "Sem novas notificações", "Sem reservas", "Sem valores pendentes",
"Senha de acesso", "Serviços", "Sistema", "Telefone", "Telemóvel", "Terminar Sessão", "Tipo",
"Total Pago", "Total Pendente", "Total agendado", "Total: {residents.length} frações registadas",
"Título do Problema", "VIA CENTRAL", "Valor", "Valor (€)", "Valor Pendente (€)", "Vencimento",
"Ver todas as Reservas", "Em Progresso", "Receita", "Despesa", "Em Validação", "Alta", "Baixa",
"Novo", "Resolvido", "Pendente", "Confirmado", "Média", "Atrasado", "Ana Silva", "Carlos Santos",
"Maria Pereira", "João Ferreira", "Sofia Costa", "Administração", "Utilizador", "Pesquisar condómino por nome ou fração...",
"Pesquisar transação...", "Pesquisar na lista...", "Pesquisar fatura...", "Escreva a sua mensagem...", "Pesquisar reserva..."
]
import urllib.request
import urllib.parse
import json
def translate_strings(texts, to_lang):
# Mocking translation with simple dictionary for crucial terms, and falling back to a placeholder or simple logic
# Realistically, the LLM will generate the translations directly.
pass
# We will let the LLM just write out the dictionary.