init: scripts diversos (crawlers, conversores, scrapers)

This commit is contained in:
2026-03-05 20:38:36 +00:00
commit 6ac6f4be2a
925 changed files with 850330 additions and 0 deletions

View File

@@ -0,0 +1,236 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
batch_process_library.py — Processa uma biblioteca inteira de traduções .po WordPress.
Executa em sequência os passos obrigatórios em TODOS os ficheiros *-pt_PT.po:
1. fix_malformed — Corrige sintaxe .po mal formada
2. fix_ptbr — Corrige PT-BR → PT-PT (saltado com --deepl, DeepL gera PT-PT nativo)
3. translate_missing — Traduz strings em falta via DeepL PT-PT (padrão) ou LibreTranslate
No final compila todos os .mo via polib e gera relatório.
Uso:
python3 batch_process_library.py /path/to/library
python3 batch_process_library.py /path/to/library --skip-translate
python3 batch_process_library.py /path/to/library --only-ptbr
python3 batch_process_library.py /path/to/library --dry-run
python3 batch_process_library.py /path/to/library --no-deepl (usar LibreTranslate)
Exemplos:
python3 batch_process_library.py /media/ealmeida/Dados/Dev/WordPress/Tradução-Plugins-PT-PT
python3 batch_process_library.py /media/ealmeida/Dados/Dev/WordPress/Tradução-Plugins-PT-PT --skip-translate
Author: Descomplicar®
Version: 2.0.0 — DeepL PT-PT
"""
import sys
import os
import subprocess
import time
import argparse
from pathlib import Path
from datetime import datetime
SCRIPT_DIR = Path(__file__).parent
FIX_MALFORMED = SCRIPT_DIR / 'fix_malformed.py'
FIX_PTBR = SCRIPT_DIR / 'fix_ptbr.py'
TRANSLATE_MISSING = SCRIPT_DIR / 'translate_missing.py'
def find_po_files(library_path: Path) -> list[Path]:
"""Encontra todos os ficheiros *-pt_PT.po na biblioteca (maxdepth 2)."""
po_files = []
for po in sorted(library_path.rglob('*-pt_PT.po')):
# Ignorar backups
if '.bak' in po.name:
continue
# Máximo 2 níveis de profundidade
rel = po.relative_to(library_path)
if len(rel.parts) <= 2:
po_files.append(po)
return po_files
def run_script(script: Path, files: list[Path], extra_args: list[str] = None) -> int:
"""Executa um script Python com lista de ficheiros."""
if not files:
return 0
cmd = ['python3', str(script)] + [str(f) for f in files]
if extra_args:
cmd += extra_args
result = subprocess.run(cmd, capture_output=False)
return result.returncode
def compile_mo(po_file: Path) -> bool:
"""Compila um .po para .mo via polib (mais permissivo que msgfmt)."""
mo_file = po_file.with_suffix('.mo')
try:
import polib
po = polib.pofile(str(po_file), encoding='utf-8')
po.save_as_mofile(str(mo_file))
return True
except Exception:
# Fallback: msgfmt
r = subprocess.run(
['msgfmt', str(po_file), '-o', str(mo_file)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
return r.returncode == 0
def count_empty_strings(po_file: Path) -> tuple[int, int]:
"""Conta strings totais e vazias num .po. Devolve (total, vazias)."""
try:
with open(po_file, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
total = content.count('\nmsgid "') + (1 if content.startswith('msgid "') else 0)
# Não contar o header (msgid "")
empty = content.count('\nmsgstr ""\n')
return max(0, total - 1), empty
except Exception:
return 0, 0
def main():
parser = argparse.ArgumentParser(
description='Processa biblioteca de traduções .po WordPress para PT-PT'
)
parser.add_argument('library', help='Caminho para a pasta com os plugins')
parser.add_argument('--skip-translate', action='store_true',
help='Saltar tradução de strings em falta (só correcções)')
parser.add_argument('--only-ptbr', action='store_true',
help='Executar apenas correcções PT-BR (sem tradução)')
parser.add_argument('--dry-run', action='store_true',
help='Simular sem gravar alterações')
parser.add_argument('--no-deepl', action='store_true',
help='Usar LibreTranslate em vez de DeepL (legado)')
args = parser.parse_args()
library = Path(args.library)
if not library.exists():
print(f"ERRO: pasta não encontrada: {library}", file=sys.stderr)
sys.exit(1)
start_time = time.time()
timestamp = datetime.now().strftime('%d-%m-%Y %H:%M')
print(f"\n{'='*65}")
print(f" Batch Process Library — PT-PT WordPress Translations")
print(f" {timestamp}")
print(f"{'='*65}")
print(f" Biblioteca: {library}")
# Encontrar todos os .po
po_files = find_po_files(library)
if not po_files:
print(f"\nNenhum ficheiro *-pt_PT.po encontrado em {library}")
sys.exit(0)
print(f" Ficheiros .po encontrados: {len(po_files)}")
# --- PASSO 1: Corrigir malformações ---
if not args.only_ptbr:
print(f"\n{''*65}")
print(f" PASSO 1/3 — Corrigir malformações de sintaxe .po")
print(f"{''*65}")
run_script(FIX_MALFORMED, po_files)
# --- PASSO 2: Corrigir PT-BR → PT-PT ---
print(f"\n{''*65}")
print(f" PASSO 2/3 — Corrigir PT-BR → PT-PT")
print(f"{''*65}")
run_script(FIX_PTBR, po_files)
# --- PASSO 3: Traduzir strings em falta ---
if not args.skip_translate and not args.only_ptbr:
engine = 'LibreTranslate' if args.no_deepl else 'DeepL PT-PT'
print(f"\n{''*65}")
print(f" PASSO 3/4 — Traduzir strings em falta via {engine}")
print(f"{''*65}")
extra = ['--dry-run'] if args.dry_run else []
run_script(TRANSLATE_MISSING, po_files, extra)
# 2ª passagem fix_ptbr — DeepL pode introduzir padrões PT-BR no output
if not args.dry_run:
print(f"\n{''*65}")
print(f" PASSO 3.5/4 — Fix PT-BR pós-tradução (2ª passagem)")
print(f"{''*65}")
run_script(FIX_PTBR, po_files)
else:
print(f"\n PASSO 3/4 — Tradução de strings: SALTADO")
# --- PASSO 4: Recompilar todos os .mo ---
if not args.dry_run:
print(f"\n{''*65}")
print(f" PASSO 4/4 — Recompilar .mo")
print(f"{''*65}")
mo_ok = 0
mo_err = 0
mo_errors = []
for po in po_files:
ok = compile_mo(po)
if ok:
mo_ok += 1
else:
mo_err += 1
mo_errors.append(po.name)
print(f" .mo compilados: {mo_ok} OK | {mo_err} com erro")
if mo_errors:
print(f" Erros: {', '.join(mo_errors[:5])}")
# --- RELATÓRIO FINAL ---
elapsed = time.time() - start_time
print(f"\n{'='*65}")
print(f" RELATÓRIO FINAL")
print(f"{'='*65}")
total_strings = 0
total_empty = 0
plugins_100 = 0
plugins_partial = 0
for po in po_files:
total, empty = count_empty_strings(po)
total_strings += total
total_empty += empty
if empty == 0:
plugins_100 += 1
else:
plugins_partial += 1
coverage = ((total_strings - total_empty) / total_strings * 100) if total_strings > 0 else 0
print(f" Plugins processados: {len(po_files)}")
print(f" 100% traduzidos: {plugins_100}")
print(f" Com strings em falta: {plugins_partial}")
print(f" Strings totais: {total_strings}")
print(f" Strings em falta: {total_empty}")
print(f" Cobertura: {coverage:.1f}%")
print(f" Tempo total: {elapsed:.0f}s")
print(f"{'='*65}\n")
# Limpar backups temporários
if not args.dry_run:
bak_count = 0
for bak in library.rglob('*.bak_*'):
bak.unlink()
bak_count += 1
if bak_count:
print(f" {bak_count} ficheiros de backup temporário removidos")
if plugins_partial > 0:
print(f"\n Plugins com strings ainda em falta:")
for po in po_files:
_, empty = count_empty_strings(po)
if empty > 0:
print(f" - {po.parent.name}: {empty} string(s) em falta")
if __name__ == '__main__':
main()