#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ fix_ptbr.py — Corrige termos PT-BR para PT-PT em ficheiros .po. Aplica substituições apenas em linhas msgstr (nunca em msgid ou comentários). Preserva placeholders e marcas registadas. Uso: python3 fix_ptbr.py plugin-pt_PT.po [plugin2-pt_PT.po ...] find /path/to/library -name "*-pt_PT.po" | xargs python3 fix_ptbr.py Author: Descomplicar® Version: 1.1.0 """ import sys import re import shutil import subprocess from pathlib import Path # Substituições PT-BR → PT-PT (padrão: word boundary para evitar falsos positivos) FIXES = [ # --- Verbos --- (r'\bsalvar\b', 'guardar'), (r'\bSalvar\b', 'Guardar'), (r'\bsalve\b', 'guarde'), (r'\bSalve\b', 'Guarde'), (r'\bativar\b', 'activar'), (r'\bAtivar\b', 'Activar'), (r'\bativado\b', 'activado'), (r'\bAtivado\b', 'Activado'), (r'\bativados\b', 'activados'), (r'\bAtivados\b', 'Activados'), (r'\bativada\b', 'activada'), (r'\bAtivada\b', 'Activada'), (r'\batualizar\b', 'actualizar'), (r'\bAtualizar\b', 'Actualizar'), (r'\batualização\b', 'actualização'), (r'\bAtualização\b', 'Actualização'), (r'\batualizações\b', 'actualizações'), (r'\bAtualizações\b', 'Actualizações'), (r'\bdesativar\b', 'desactivar'), (r'\bDesativar\b', 'Desactivar'), (r'\bdesativado\b', 'desactivado'), (r'\bDesativado\b', 'Desactivado'), (r'\bdesativada\b', 'desactivada'), (r'\bDesativada\b', 'Desactivada'), (r'\bconectar\b', 'ligar'), (r'\bConectar\b', 'Ligar'), (r'\bconectado\b', 'ligado'), (r'\bConectado\b', 'Ligado'), (r'\bdesconectar\b', 'desligar'), (r'\bDesconectar\b', 'Desligar'), (r'\bdesconectado\b', 'desligado'), (r'\bDesconectado\b', 'Desligado'), (r'\breconectar\b', 'religar'), (r'\bReconectar\b', 'Religar'), (r'\bselecionar\b', 'seleccionar'), (r'\bSelecionar\b', 'Seleccionar'), (r'\bexcluir\b', 'eliminar'), (r'\bExcluir\b', 'Eliminar'), (r'\bexcluído\b', 'eliminado'), (r'\bExcluído\b', 'Eliminado'), (r'\bexcluída\b', 'eliminada'), (r'\bExcluída\b', 'Eliminada'), (r'\bexcluídos\b', 'eliminados'), (r'\bExcluídos\b', 'Eliminados'), (r'\bexcluídas\b', 'eliminadas'), (r'\bExcluídas\b', 'Eliminadas'), (r'\bacessar\b', 'aceder'), (r'\bAcessar\b', 'Aceder'), (r'\bcompartilhar\b', 'partilhar'), (r'\bCompartilhar\b', 'Partilhar'), (r'\bcompartilhado\b', 'partilhado'), (r'\bCompartilhado\b', 'Partilhado'), (r'\bcompartilhada\b', 'partilhada'), (r'\bCompartilhada\b', 'Partilhada'), (r'\bcompartilhados\b', 'partilhados'), (r'\bCompartilhados\b', 'Partilhados'), (r'\bcompartilhadas\b', 'partilhadas'), (r'\bCompartilhadas\b', 'Partilhadas'), (r'\bdigite\b', 'introduza'), (r'\bDigite\b', 'Introduza'), # --- Substantivos --- (r'\bsenha\b', 'palavra-passe'), (r'\bSenha\b', 'Palavra-passe'), (r'\bsenhas\b', 'palavras-passe'), (r'\bSenhas\b', 'Palavras-passe'), (r'\busuário\b', 'utilizador'), (r'\bUsuário\b', 'Utilizador'), (r'\busuários\b', 'utilizadores'), (r'\bUsuários\b', 'Utilizadores'), (r'\busuario\b', 'utilizador'), (r'\bUsuario\b', 'Utilizador'), (r'\barquivo\b', 'ficheiro'), (r'\bArquivo\b', 'Ficheiro'), (r'\barquivos\b', 'ficheiros'), (r'\bArquivos\b', 'Ficheiros'), (r'\bconexão\b', 'ligação'), (r'\bConexão\b', 'Ligação'), (r'\bseleção\b', 'selecção'), (r'\bSeleção\b', 'Selecção'), (r'\bselecionado\b', 'seleccionado'), (r'\bSelecionado\b', 'Seleccionado'), (r'\baplicativo\b', 'aplicação'), (r'\bAplicativo\b', 'Aplicação'), (r'\baplicativos\b', 'aplicações'), (r'\bAplicativos\b', 'Aplicações'), (r'\bdashboard\b', 'painel'), (r'\bDashboard\b', 'Painel'), (r'\bbackup\b', 'cópia de segurança'), (r'\bBackup\b', 'Cópia de segurança'), (r'\bbackups\b', 'cópias de segurança'), (r'\bBackups\b', 'Cópias de segurança'), (r'\bpostagem\b', 'publicação'), (r'\bPostagem\b', 'Publicação'), (r'\bpostagens\b', 'publicações'), (r'\bPostagens\b', 'Publicações'), (r'\bativo\b', 'activo'), (r'\bAtivo\b', 'Activo'), (r'\bativos\b', 'activos'), (r'\bAtivos\b', 'Activos'), (r'\bativa\b', 'activa'), (r'\bAtiva\b', 'Activa'), (r'\bativas\b', 'activas'), (r'\bAtivas\b', 'Activas'), (r'\binativo\b', 'inactivo'), (r'\bInativo\b', 'Inactivo'), (r'\binativos\b', 'inactivos'), (r'\bInativos\b', 'Inactivos'), (r'\binativa\b', 'inactiva'), (r'\bInativa\b', 'Inactiva'), (r'\binativas\b', 'inactivas'), (r'\bInativas\b', 'Inactivas'), (r'\bativação\b', 'activação'), (r'\bAtivação\b', 'Activação'), (r'\bdesativação\b', 'desactivação'), (r'\bDesativação\b', 'Desactivação'), # --- Ortografia PT-PT --- # Formas garbled LibreTranslate (Updated→Atualizard, Updates→Atualizars) (r'\bAtualizard\b', 'Actualizado'), (r'\batualizard\b', 'actualizado'), (r'\bAtualizars\b', 'Actualizações'), (r'\batualizars\b', 'actualizações'), # Formas maiúsculas (all-caps) (r'\bATUALIZAR\b', 'ACTUALIZAR'), (r'\bATUALIZAÇÃO\b', 'ACTUALIZAÇÃO'), (r'\bATIVAR\b', 'ACTIVAR'), (r'\bDESATIVAR\b', 'DESACTIVAR'), (r'\bATIVADO\b', 'ACTIVADO'), (r'\bDESATIVADO\b', 'DESACTIVADO'), (r'\bARQUIVO\b', 'FICHEIRO'), (r'\bARQUIVOS\b', 'FICHEIROS'), # Todas as formas de atualiz* → actualiz* (r'\batualizada\b', 'actualizada'), (r'\bAtualizada\b', 'Actualizada'), (r'\batualizadas\b', 'actualizadas'), (r'\bAtualizadas\b', 'Actualizadas'), (r'\batualizado\b', 'actualizado'), (r'\bAtualizado\b', 'Actualizado'), (r'\batualizados\b', 'actualizados'), (r'\bAtualizados\b', 'Actualizados'), (r'\batualize\b', 'actualize'), (r'\bAtualize\b', 'Actualize'), (r'\batualiza\b', 'actualiza'), (r'\bAtualiza\b', 'Actualiza'), (r'\batualizando\b', 'actualizando'), (r'\bAtualizando\b', 'Actualizando'), (r'\batualizou\b', 'actualizou'), (r'\bAtualizou\b', 'Actualizou'), (r'\batualizem\b', 'actualizem'), (r'\bAtualizem\b', 'Actualizem'), (r'\batualizam\b', 'actualizam'), (r'\bAtualizam\b', 'Actualizam'), (r'\batualizamos\b', 'actualizamos'), (r'\bAtualizamos\b', 'Actualizamos'), (r'\batualizei\b', 'actualizei'), (r'\bAtualizei\b', 'Actualizei'), (r'\batualizará\b', 'actualizará'), (r'\bAtualizará\b', 'Actualizará'), (r'\batualizarão\b', 'actualizarão'), (r'\bAtualizarão\b', 'Actualizarão'), (r'\batualizarei\b', 'actualizarei'), (r'\bAtualizarei\b', 'Actualizarei'), (r'\batualizaremos\b', 'actualizaremos'), (r'\bAtualizaremos\b', 'Actualizaremos'), (r'\batualizarás\b', 'actualizarás'), (r'\batualizassem\b', 'actualizassem'), # Formas com acento (atualizá-los, atualizá-la) (r'\batualizá\b', 'actualizá'), (r'\bAtualizá\b', 'Actualizá'), # Habilitar/desabilitar → activar/desactivar (r'\bhabilitado\b', 'activado'), (r'\bHabilitado\b', 'Activado'), (r'\bhabilitada\b', 'activada'), (r'\bHabilitada\b', 'Activada'), (r'\bhabilitados\b', 'activados'), (r'\bHabilitados\b', 'Activados'), (r'\bhabilitadas\b', 'activadas'), (r'\bHabilitadas\b', 'Activadas'), (r'\bhabilitar\b', 'activar'), (r'\bHabilitar\b', 'Activar'), (r'\bdesabilitado\b', 'desactivado'), (r'\bDesabilitado\b', 'Desactivado'), (r'\bdesabilitada\b', 'desactivada'), (r'\bDesabilitada\b', 'Desactivada'), (r'\bdesabilitados\b', 'desactivados'), (r'\bDesabilitados\b', 'Desactivados'), (r'\bdesabilitar\b', 'desactivar'), (r'\bDesabilitar\b', 'Desactivar'), (r'\bdesabilitadas\b', 'desactivadas'), (r'\bDesabilitadas\b', 'Desactivadas'), # Formas plurais de desativar (r'\bdesativados\b', 'desactivados'), (r'\bDesativados\b', 'Desactivados'), (r'\bdesativadas\b', 'desactivadas'), (r'\bDesativadas\b', 'Desactivadas'), (r'\bdesativando\b', 'desactivando'), (r'\bDesativando\b', 'Desactivando'), # Formas plurais de ativar (r'\bativados\b', 'activados'), (r'\bAtivados\b', 'Activados'), (r'\bativadas\b', 'activadas'), (r'\bAtivadas\b', 'Activadas'), (r'\bativando\b', 'activando'), (r'\bAtivando\b', 'Activando'), (r'\bfatura\b', 'factura'), (r'\bFatura\b', 'Factura'), (r'\bfaturas\b', 'facturas'), (r'\bFaturas\b', 'Facturas'), (r'\bfacturar\b', 'facturar'), (r'\bóptico\b', 'óptico'), (r'\bótimo\b', 'óptimo'), (r'\bÓtimo\b', 'Óptimo'), (r'\bação\b', 'acção'), (r'\bAção\b', 'Acção'), (r'\bações\b', 'acções'), (r'\bAções\b', 'Acções'), (r'\bdireito\b', 'direito'), # igual — sem alteração necessária (r'\bdiretamente\b', 'directamente'), (r'\bDiretamente\b', 'Directamente'), (r'\bdireto\b', 'directo'), (r'\bDireto\b', 'Directo'), (r'\bcontacto\b', 'contacto'), # já correcto # --- Pronomes PT-BR --- (r'\bVOCÊ\b', 'SI'), (r'\bVOCÊS\b', 'VÓS'), (r'\bvocê\b', 'o utilizador'), (r'\bVocê\b', 'O utilizador'), (r'\bvocês\b', 'os utilizadores'), (r'\bVocês\b', 'Os utilizadores'), # --- Expressões compostas --- (r'\bsua conta\b', 'a sua conta'), (r'\bSua conta\b', 'A sua conta'), (r'\bnome de usuário\b', 'nome de utilizador'), (r'\bNome de usuário\b', 'Nome de utilizador'), (r'\bnome de usuario\b', 'nome de utilizador'), ] def fix_line(line: str) -> str: """Aplica todas as substituições PT-BR → PT-PT numa linha.""" result = line for pattern, replacement in FIXES: result = re.sub(pattern, replacement, result) return result def process_file(filepath: str) -> tuple[int, bool]: """ Processa um ficheiro .po e corrige PT-BR → PT-PT. Devolve (número de correcções, sucesso do .mo). """ path = Path(filepath) if not path.exists(): print(f" ERRO: ficheiro não encontrado: {filepath}", file=sys.stderr) return 0, False with open(filepath, 'r', encoding='utf-8') as f: lines = f.readlines() new_lines = [] in_msgstr = False fixes = 0 for line in lines: stripped = line.rstrip('\n') if stripped.startswith('msgstr ') or re.match(r'^msgstr\[\d+\]', stripped): in_msgstr = True fixed = fix_line(stripped) if fixed != stripped: fixes += 1 new_lines.append(fixed + '\n') elif stripped.startswith('"') and in_msgstr: fixed = fix_line(stripped) if fixed != stripped: fixes += 1 new_lines.append(fixed + '\n') else: if not stripped or stripped.startswith('#') or \ stripped.startswith('msgid') or stripped.startswith('msgctxt'): in_msgstr = False new_lines.append(line) if fixes > 0: shutil.copy2(filepath, filepath + '.bak_ptbr') with open(filepath, 'w', encoding='utf-8') as f: f.writelines(new_lines) mo_path = filepath.replace('.po', '.mo') r = subprocess.run( ['msgfmt', filepath, '-o', mo_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) mo_ok = r.returncode == 0 mo_status = "OK" if mo_ok else f"ERRO: {r.stderr.decode()[:80]}" print(f" {path.name}: {fixes} correcção(ões) PT-BR | .mo: {mo_status}") return fixes, mo_ok else: print(f" {path.name}: sem PT-BR detectado") return 0, True def main(): if len(sys.argv) < 2: print("Uso: python3 fix_ptbr.py [...]") print(" find /path -name '*-pt_PT.po' | xargs python3 fix_ptbr.py") sys.exit(1) total_fixes = 0 total_files = 0 errors = 0 for filepath in sys.argv[1:]: total_files += 1 fixes, ok = process_file(filepath) total_fixes += fixes if not ok: errors += 1 print(f"\nTotal: {total_fixes} correcção(ões) em {total_files} ficheiro(s) | Erros .mo: {errors}") if __name__ == '__main__': main()