From 561bb1473680556e882e845e2b7f588f6d329bd0 Mon Sep 17 00:00:00 2001 From: Emanuel Almeida Date: Sat, 29 Aug 2026 02:29:44 +0100 Subject: [PATCH] =?UTF-8?q?fix(wp-translate-ptpt):=20reparar=20em=20vez=20?= =?UTF-8?q?de=20reverter=20corrup=C3=A7=C3=A3o=20de=20placeholders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- translate-wp-plugin/wp-translate-ptpt.py | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/translate-wp-plugin/wp-translate-ptpt.py b/translate-wp-plugin/wp-translate-ptpt.py index d08c9dd..35ec372 100755 --- a/translate-wp-plugin/wp-translate-ptpt.py +++ b/translate-wp-plugin/wp-translate-ptpt.py @@ -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")