219 lines
10 KiB
PHP
219 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* translate_lectura_titles.php (issue Gitea #140)
|
|
*
|
|
* Traduce SOLO el nombre del libro bíblico en el INICIO del post_title de los
|
|
* posts no-ES (EN/FR/IT/PT) cuyo título es una cita bíblica «<LIBRO> <num>, ...».
|
|
* El cuerpo ya está traducido; esto es title-only.
|
|
*
|
|
* - Determinista: mapa fijo de libros ES -> {en,fr,it,pt}.
|
|
* - Idempotente: si el token inicial ya está en el idioma destino, no toca nada.
|
|
* - Seguro: exige número tras el libro (excluye «Juan Pablo II», «Domingo 30...»,
|
|
* etc. — DOMINGO/SEMANA no son libros, no están en el mapa).
|
|
* - Cotejo insensible a acentos (fold a ASCII-mayúsculas) para casar variantes;
|
|
* el valor canónico por idioma garantiza que ES==destino sea un no-op.
|
|
*
|
|
* Uso (local): docker exec wordpress-web php /var/www/html/scripts/... (o vía cwd)
|
|
* php scripts/translate_lectura_titles.php # dry-run + reporte
|
|
* APPLY=1 php scripts/translate_lectura_titles.php # aplica
|
|
* Prod: FEA_WP_LOAD=/web/wp-nuevo/wp-load.php php translate_lectura_titles.php
|
|
*/
|
|
|
|
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE);
|
|
|
|
$WP_LOAD = getenv('FEA_WP_LOAD') ?: '/var/www/html/wp-load.php';
|
|
if (!file_exists($WP_LOAD)) {
|
|
fwrite(STDERR, "No encuentro wp-load.php en $WP_LOAD\n");
|
|
exit(1);
|
|
}
|
|
define('WP_USE_THEMES', false);
|
|
require $WP_LOAD;
|
|
|
|
global $wpdb;
|
|
$APPLY = getenv('APPLY') === '1';
|
|
$LANGS = ['en', 'fr', 'it', 'pt'];
|
|
|
|
/*
|
|
* Mapa de libros: clave en español (display) => [en, fr, it, pt] (Title case canónico).
|
|
* El cotejo es accent-insensitive; los valores destino son los litúrgicos católicos.
|
|
*/
|
|
$BOOKS = [
|
|
// --- Antiguo Testamento ---
|
|
'Génesis' => ['Genesis', 'Genèse', 'Genesi', 'Gênesis'],
|
|
'Éxodo' => ['Exodus', 'Exode', 'Esodo', 'Êxodo'],
|
|
'Levítico' => ['Leviticus', 'Lévitique', 'Levitico', 'Levítico'],
|
|
'Números' => ['Numbers', 'Nombres', 'Numeri', 'Números'],
|
|
'Deuteronomio' => ['Deuteronomy', 'Deutéronome', 'Deuteronomio', 'Deuteronômio'],
|
|
'Josué' => ['Joshua', 'Josué', 'Giosuè', 'Josué'],
|
|
'Jueces' => ['Judges', 'Juges', 'Giudici', 'Juízes'],
|
|
'Rut' => ['Ruth', 'Ruth', 'Rut', 'Rute'],
|
|
'Samuel' => ['Samuel', 'Samuel', 'Samuele', 'Samuel'],
|
|
'Reyes' => ['Kings', 'Rois', 'Re', 'Reis'],
|
|
'Crónicas' => ['Chronicles', 'Chroniques', 'Cronache', 'Crônicas'],
|
|
'Esdras' => ['Ezra', 'Esdras', 'Esdra', 'Esdras'],
|
|
'Nehemías' => ['Nehemiah', 'Néhémie', 'Neemia', 'Neemias'],
|
|
'Tobías' => ['Tobit', 'Tobie', 'Tobia', 'Tobias'],
|
|
'Judit' => ['Judith', 'Judith', 'Giuditta', 'Judite'],
|
|
'Ester' => ['Esther', 'Esther', 'Ester', 'Ester'],
|
|
'Macabeos' => ['Maccabees', 'Maccabées', 'Maccabei', 'Macabeus'],
|
|
'Job' => ['Job', 'Job', 'Giobbe', 'Jó'],
|
|
'Salmos' => ['Psalms', 'Psaumes', 'Salmi', 'Salmos'],
|
|
'Salmo' => ['Psalm', 'Psaume', 'Salmo', 'Salmo'],
|
|
'Proverbios' => ['Proverbs', 'Proverbes', 'Proverbi', 'Provérbios'],
|
|
'Eclesiastés' => ['Ecclesiastes', 'Ecclésiaste', 'Ecclesiaste', 'Eclesiastes'],
|
|
'Eclesiástico' => ['Ecclesiasticus', 'Siracide', 'Siracide', 'Eclesiástico'],
|
|
'Sabiduría' => ['Wisdom', 'Sagesse', 'Sapienza', 'Sabedoria'],
|
|
'Isaías' => ['Isaiah', 'Isaïe', 'Isaia', 'Isaías'],
|
|
'Jeremías' => ['Jeremiah', 'Jérémie', 'Geremia', 'Jeremias'],
|
|
'Lamentaciones' => ['Lamentations', 'Lamentations', 'Lamentazioni', 'Lamentações'],
|
|
'Baruc' => ['Baruch', 'Baruch', 'Baruc', 'Baruc'],
|
|
'Ezequiel' => ['Ezekiel', 'Ézéchiel', 'Ezechiele', 'Ezequiel'],
|
|
'Daniel' => ['Daniel', 'Daniel', 'Daniele', 'Daniel'],
|
|
'Oseas' => ['Hosea', 'Osée', 'Osea', 'Oseias'],
|
|
'Joel' => ['Joel', 'Joël', 'Gioele', 'Joel'],
|
|
'Amós' => ['Amos', 'Amos', 'Amos', 'Amós'],
|
|
'Abdías' => ['Obadiah', 'Abdias', 'Abdia', 'Abdias'],
|
|
'Jonás' => ['Jonah', 'Jonas', 'Giona', 'Jonas'],
|
|
'Miqueas' => ['Micah', 'Michée', 'Michea', 'Miqueias'],
|
|
'Nahúm' => ['Nahum', 'Nahum', 'Naum', 'Naum'],
|
|
'Habacuc' => ['Habakkuk', 'Habacuc', 'Abacuc', 'Habacuc'],
|
|
'Sofonías' => ['Zephaniah', 'Sophonie', 'Sofonia', 'Sofonias'],
|
|
'Ageo' => ['Haggai', 'Aggée', 'Aggeo', 'Ageu'],
|
|
'Zacarías' => ['Zechariah', 'Zacharie', 'Zaccaria', 'Zacarias'],
|
|
'Malaquías' => ['Malachi', 'Malachie', 'Malachia', 'Malaquias'],
|
|
// --- Nuevo Testamento ---
|
|
'Mateo' => ['Matthew', 'Matthieu', 'Matteo', 'Mateus'],
|
|
'Marcos' => ['Mark', 'Marc', 'Marco', 'Marcos'],
|
|
'Lucas' => ['Luke', 'Luc', 'Luca', 'Lucas'],
|
|
'Juan' => ['John', 'Jean', 'Giovanni', 'João'],
|
|
'Hechos' => ['Acts', 'Actes', 'Atti', 'Atos'],
|
|
'Romanos' => ['Romans', 'Romains', 'Romani', 'Romanos'],
|
|
'Corintios' => ['Corinthians', 'Corinthiens', 'Corinzi', 'Coríntios'],
|
|
'Gálatas' => ['Galatians', 'Galates', 'Galati', 'Gálatas'],
|
|
'Efesios' => ['Ephesians', 'Éphésiens', 'Efesini', 'Efésios'],
|
|
'Filipenses' => ['Philippians', 'Philippiens', 'Filippesi', 'Filipenses'],
|
|
'Colosenses' => ['Colossians', 'Colossiens', 'Colossesi', 'Colossenses'],
|
|
'Tesalonicenses' => ['Thessalonians', 'Thessaloniciens', 'Tessalonicesi', 'Tessalonicenses'],
|
|
'Timoteo' => ['Timothy', 'Timothée', 'Timoteo', 'Timóteo'],
|
|
'Tito' => ['Titus', 'Tite', 'Tito', 'Tito'],
|
|
'Filemón' => ['Philemon', 'Philémon', 'Filemone', 'Filêmon'],
|
|
'Hebreos' => ['Hebrews', 'Hébreux', 'Ebrei', 'Hebreus'],
|
|
'Santiago' => ['James', 'Jacques', 'Giacomo', 'Tiago'],
|
|
'Pedro' => ['Peter', 'Pierre', 'Pietro', 'Pedro'],
|
|
'Judas' => ['Jude', 'Jude', 'Giuda', 'Judas'],
|
|
'Apocalipsis' => ['Revelation', 'Apocalypse', 'Apocalisse', 'Apocalipse'],
|
|
];
|
|
|
|
// fold a ASCII-mayúsculas (quita acentos) para cotejo robusto
|
|
function fold($s) {
|
|
$s = trim($s);
|
|
$map = ['Á'=>'A','À'=>'A','Ä'=>'A','Â'=>'A','Ã'=>'A','É'=>'E','È'=>'E','Ë'=>'E','Ê'=>'E',
|
|
'Í'=>'I','Ì'=>'I','Ï'=>'I','Î'=>'I','Ó'=>'O','Ò'=>'O','Ö'=>'O','Ô'=>'O','Õ'=>'O',
|
|
'Ú'=>'U','Ù'=>'U','Ü'=>'U','Û'=>'U','Ñ'=>'N','Ç'=>'C'];
|
|
$s = mb_strtoupper($s, 'UTF-8');
|
|
return strtr($s, $map);
|
|
}
|
|
|
|
$langIdx = array_flip($LANGS); // en=>0,...
|
|
// índice de búsqueda: foldedSpanish => [en,fr,it,pt]
|
|
$LOOKUP = [];
|
|
foreach ($BOOKS as $es => $tr) {
|
|
$LOOKUP[fold($es)] = $tr;
|
|
}
|
|
|
|
// Todos los posts no-ES (filtramos/transformamos en PHP, regex /u fiable).
|
|
$placeholders = implode(',', array_fill(0, count($LANGS), '%s'));
|
|
$sql = $wpdb->prepare(
|
|
"SELECT p.ID, t.slug AS lang, p.post_title
|
|
FROM {$wpdb->posts} p
|
|
JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
|
|
JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id AND tt.taxonomy='language'
|
|
JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
|
|
WHERE p.post_type='post'
|
|
AND p.post_status IN ('publish','draft','future','pending','private')
|
|
AND t.slug IN ($placeholders)",
|
|
$LANGS
|
|
);
|
|
$rows = $wpdb->get_results($sql);
|
|
|
|
$changes = []; // [ID => [lang, old, new]]
|
|
$per_lang = array_fill_keys($LANGS, 0);
|
|
$skipped_already = 0;
|
|
$candidates = 0; // títulos con al menos una cita bíblica detectada
|
|
|
|
/*
|
|
* Traduce CADA token de libro de una cita bíblica dentro del título:
|
|
* - una palabra (letras) NO precedida por letra/número
|
|
* - seguida de «<espacios><dígito>» (el capítulo de la cita).
|
|
* Cubre el inicio, los compuestos «1ª lectura / 2ª lectura / evangelio» (tras «/»),
|
|
* el ordinal previo («2 Timoteo 4») y prefijos de fiesta («Epifanía - Isaías 60»).
|
|
* Como SOLO casa ortografías españolas (las del mapa), en posts no-ES únicamente
|
|
* toca citas heredadas del ES; las descripciones van en el idioma destino.
|
|
*/
|
|
$BOOK_RE = '/(?<![\p{L}\p{N}])(\p{L}[\p{L}.]*)(?=\s+\d)/u';
|
|
|
|
foreach ($rows as $r) {
|
|
$lang = $r->lang;
|
|
$li = $langIdx[$lang];
|
|
$hit = false;
|
|
$new_title = preg_replace_callback($BOOK_RE, function ($m) use ($LOOKUP, $li, &$hit) {
|
|
$book = $m[1];
|
|
$key = fold($book);
|
|
if (!isset($LOOKUP[$key])) {
|
|
return $m[0]; // no es libro conocido -> intacto
|
|
}
|
|
$hit = true;
|
|
$canon = $LOOKUP[$key][$li];
|
|
$isUpper = (mb_strtoupper($book, 'UTF-8') === $book);
|
|
return $isUpper ? mb_strtoupper($canon, 'UTF-8') : $canon;
|
|
}, $r->post_title);
|
|
|
|
if (!$hit) { continue; }
|
|
$candidates++;
|
|
if ($new_title === $r->post_title) { $skipped_already++; continue; } // idempotente
|
|
|
|
$changes[$r->ID] = [$lang, $r->post_title, $new_title];
|
|
$per_lang[$lang]++;
|
|
}
|
|
|
|
// --- Reporte ---
|
|
echo "== translate_lectura_titles.php (issue #140) ==\n";
|
|
echo "WP_LOAD: $WP_LOAD\n";
|
|
echo "Posts no-ES escaneados: " . count($rows) . "\n";
|
|
echo " - con cita bíblica detectada: $candidates\n";
|
|
echo " - ya en idioma destino (idempotente): $skipped_already\n";
|
|
echo " - A CAMBIAR: " . count($changes) . "\n";
|
|
echo " por idioma: ";
|
|
foreach ($LANGS as $l) echo strtoupper($l) . "=" . $per_lang[$l] . " ";
|
|
echo "\n\n";
|
|
|
|
$SAMPLE = (int)(getenv('SAMPLE') ?: 8);
|
|
foreach ($LANGS as $l) {
|
|
$shown = 0;
|
|
echo "--- muestra $l ---\n";
|
|
foreach ($changes as $id => $c) {
|
|
if ($c[0] !== $l) continue;
|
|
echo sprintf(" %d «%s» -> «%s»\n", $id, $c[1], $c[2]);
|
|
if ($SAMPLE > 0 && ++$shown >= $SAMPLE) break;
|
|
}
|
|
}
|
|
echo "\n";
|
|
|
|
if (!$APPLY) {
|
|
echo "DRY-RUN (no se ha tocado nada). Ejecuta con APPLY=1 para aplicar.\n";
|
|
exit(0);
|
|
}
|
|
|
|
// --- Aplica (title-only, sin tocar slug/cuerpo) ---
|
|
$done = 0;
|
|
foreach ($changes as $id => $c) {
|
|
$ok = $wpdb->update($wpdb->posts, ['post_title' => $c[2]], ['ID' => $id], ['%s'], ['%d']);
|
|
if ($ok !== false) {
|
|
clean_post_cache($id);
|
|
$done++;
|
|
} else {
|
|
fwrite(STDERR, "ERROR al actualizar ID $id\n");
|
|
}
|
|
}
|
|
echo "APLICADOS: $done de " . count($changes) . "\n";
|