init: scripts diversos (crawlers, conversores, scrapers)
This commit is contained in:
236
translate-wp-plugin/batch_process_library.py
Executable file
236
translate-wp-plugin/batch_process_library.py
Executable 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()
|
||||
Reference in New Issue
Block a user