Sync: guardar en el historial el trabajo de las ultimas semanas que solo vivia en el disco

Este repo local tenia origin apuntando al Gitea local (localhost:3000), que Rafa
declaro archivado el 2026-06-28 (commit 962f33a, desarrollo movido a
gitea.feadulta.com). Ese memo nunca llego a este checkout: main quedo congelado
y todo el trabajo real de las ultimas 3+ semanas se fue commiteando solo en la
rama fix/multiidioma-portada-132 (ya fusionada a main sin perdida, commit
2504666), mientras que ademas se acumulaban 78 cambios sin commitear en el
working tree que nunca llegaron a NINGUN historial de git.

Este commit consolida esos cambios sueltos: TTS multi-voz (tts_*.py), scripts
de traduccion (translate_haiku.py, pretranslate_en_haiku.py, sync_translations_to_prod.py),
mu-plugins nuevos desplegados a prod (fea-beta-feedback, fea-cloudflare-realip,
fea-legacy-redirect, fea-gsc-verification, fea-support-campaign, fea-ui, etc.),
scripts de mantenimiento de enlaces/cartas, capturas E2E (tools/e2e/shot_*.cjs)
y documentacion de sesiones recientes.

Excluido deliberadamente (no es codigo versionable): tts-voices/ (3.3GB de
muestras de audio para clonacion de voz, anadido a .gitignore), logs/ (logs de
ejecucion, anadido a .gitignore), y 2 ficheros vacios accidentales + 2 copias
duplicadas sueltas en la raiz que ya existen en su ubicacion correcta.
This commit is contained in:
2026-07-15 20:03:21 -04:00
parent 250466696b
commit 69e849d38e
72 changed files with 5413 additions and 220 deletions
+77
View File
@@ -0,0 +1,77 @@
<?php
/**
* Issue #80 — detecta traducciones con fragmentos en español sin traducir.
* Señal: una frase sin traducir queda IDÉNTICA a la del ES original.
* Para cada traducción (en/fr/it/pt) compara sus frases contra el set de frases
* del ES enlazado (Polylang) y calcula el % de caracteres que coinciden literal.
*
* Uso (en contenedor): php detect_untranslated.php [umbral] [status]
* umbral: ratio mínimo para marcar (def 0.12)
* status: draft (def) | publish | any
*/
require "/var/www/html/wp-load.php";
global $wpdb;
$THRESH = isset($argv[1]) ? (float)$argv[1] : 0.12;
$STATUS = $argv[2] ?? 'draft';
function norm_text($html) {
$t = preg_replace('~(?i)</p>|<br\s*/?>|</h[1-6]>~', "\n", $html);
$t = preg_replace('~<[^>]+>~', ' ', $t);
$t = preg_replace('~\[[^\]]+\]~', ' ', $t);
$t = html_entity_decode($t, ENT_QUOTES);
return $t;
}
/** Frases normalizadas de longitud >= 40 (las cortas dan falsos positivos). */
function sentences($html) {
$t = norm_text($html);
$parts = preg_split('~(?<=[.!?…])\s+|\n+~u', $t);
$out = [];
foreach ($parts as $s) {
$s = trim(preg_replace('~\s+~u', ' ', $s));
$s = mb_strtolower($s);
if (mb_strlen($s) >= 40) $out[$s] = mb_strlen($s);
}
return $out;
}
$statuses = $STATUS === 'any' ? ['draft','publish'] : [$STATUS];
$in = "'" . implode("','", $statuses) . "'";
$ids = $wpdb->get_col(
"SELECT p.ID FROM wp_posts p
JOIN wp_term_relationships tr ON tr.object_id=p.ID
JOIN wp_term_taxonomy tt ON tt.term_taxonomy_id=tr.term_taxonomy_id AND tt.taxonomy='language'
JOIN wp_terms t ON t.term_id=tt.term_id AND t.slug IN ('en','fr','it','pt')
WHERE p.post_type='post' AND p.post_status IN ($in)
GROUP BY p.ID"
);
$by_lang = []; $offenders = [];
foreach ($ids as $id) {
$lang = pll_get_post_language($id);
$es = pll_get_post((int)$id, 'es');
if (!$es) continue;
$tr_s = sentences(get_post($id)->post_content);
if (!$tr_s) continue;
$es_s = sentences(get_post($es)->post_content);
if (!$es_s) continue;
$total = array_sum($tr_s); $match = 0;
foreach ($tr_s as $s => $len) if (isset($es_s[$s])) $match += $len;
$ratio = $total ? $match / $total : 0;
$by_lang[$lang]['n'] = ($by_lang[$lang]['n'] ?? 0) + 1;
if ($ratio >= $THRESH) {
$by_lang[$lang]['bad'] = ($by_lang[$lang]['bad'] ?? 0) + 1;
$offenders[] = [$id, $lang, $es, round($ratio, 2), get_post($id)->post_title];
}
}
usort($offenders, fn($a, $b) => $b[3] <=> $a[3]);
echo "=== Traducciones con fragmentos ES (ratio >= $THRESH, status=$STATUS) ===\n";
foreach ($offenders as $o)
echo sprintf("#%d [%s] ratio=%.2f es=%d %s\n", $o[0], $o[1], $o[3], $o[2], mb_substr($o[4], 0, 45));
echo "\n--- resumen por idioma ---\n";
foreach ($by_lang as $l => $d)
echo sprintf("%s: %d/%d con fragmentos ES\n", $l, $d['bad'] ?? 0, $d['n']);
echo "TOTAL ofensores: " . count($offenders) . "\n";
// Volcar IDs para el reprocesado
file_put_contents('/tmp/untranslated_ids.txt', implode("\n", array_map(fn($o) => $o[0], $offenders)));