Compare commits

..
1 Commits
Author SHA1 Message Date
ealmeida 561bb14736 fix(wp-translate-ptpt): reparar em vez de reverter corrupção de placeholders
QualityValidator.validate_entry() detectava PLACEHOLDER_MISMATCH mas só
revertia para o msgstr original — se a entrada já chegasse corrompida
(de fora do pipeline, ou de execuções anteriores do LibreTranslate), o
bug ficava preservado silenciosamente em vez de corrigido.

Adiciona repair_placeholders(), chamado no início de validate_entry():
- '%1$s' -> '% 1$s' (espaço após % antes do posicional) -> reparado
- '%1$.2f' -> '% 1$. 2f' (espaço após o ponto decimal) -> reparado

Âncora obrigatória: \d+\$ (posicional). Nunca ocorre em prosa PT-PT,
por isso zero falsos positivos em texto legítimo (testado contra '50%
do total', '30% em vendas', etc. — ficam inalterados).

Testado contra as 4 strings reais encontradas corrompidas em produção
(testimonial-pro, woocommerce, wordfence, wp-optimize) — todas reparam
para o valor correcto exacto.

Causa raiz: motor LibreTranslate (TranslationEngine.translate) recebe
placeholders crus, sem tokenização — ao contrário de translate_missing.py
(DeepL) que protege com tokens ⟦N⟧. Reparação aqui é mitigação; a
tokenização do motor LibreTranslate fica como follow-up.
2026-08-29 02:29:44 +01:00
+28
View File
@@ -564,10 +564,38 @@ class QualityValidator:
'visualizar', 'acessar', 'baixar', 'compartilhar'
]
# Corrupção conhecida introduzida por motores de MT (LibreTranslate/DeepL):
# inserem espaços dentro de especificadores printf posicionais, ex.
# "%1$s" -> "% 1$s", "%1$.2f" -> "% 1$. 2f". sprintf() do PHP 8 lança
# ValueError com isto -> fatal error no site.
#
# Âncora obrigatória: \d+\$ (posicional). Nunca ocorre em prosa PT-PT —
# "%" seguido de espaço e dígito sozinho (ex. "% d" em "50% do total")
# É ambíguo com texto legítimo, por isso NÃO é corrigido automaticamente.
REPAIRABLE_PATTERNS = [
(r'%\s+(\d+\$)', r'%\1'), # "% 1$s" -> "%1$s"
(r'(\d+\$)\.\s+(\d)', r'\1.\2'), # "1$. 2f" -> "1$.2f"
]
def repair_placeholders(self, msgstr: str) -> str:
"""Corrige automaticamente corrupções conhecidas e seguras de placeholders."""
repaired = msgstr
for pattern, replacement in self.REPAIRABLE_PATTERNS:
repaired = re.sub(pattern, replacement, repaired)
return repaired
def validate_entry(self, entry: PoEntry) -> Tuple[bool, List[str]]:
"""Validate a complete entry."""
errors = []
# 0. Reparar corrupções conhecidas ANTES de validar. Isto cobre tanto
# traduções novas como entradas já corrompidas revalidadas em modo
# --brands-only — sem isto, o mismatch era apenas detectado e a
# entrada revertida para o original (que já vinha corrompido),
# preservando o bug silenciosamente em vez de o corrigir.
if entry.msgstr:
entry.msgstr = self.repair_placeholders(entry.msgstr)
# 1. Check placeholders
if not self._check_placeholders(entry.msgid, entry.msgstr):
errors.append("PLACEHOLDER_MISMATCH")