Mise à jour de travail sur le portage de la version 'Ovh' vers le VPS. Nombreuses modifs à tous niveaux (donc non tracées...)

This commit is contained in:
2026-10-02 15:20:14 +00:00
parent 94ae0d9b25
commit fcf5434ff8
15 changed files with 855 additions and 751 deletions
+243 -19
View File
@@ -8,18 +8,181 @@
// - Les nouvelles chaînes (trouvées dans le code, absentes de en.php)
// sont ajoutées avec une valeur vide, à traduire manuellement.
// - Les entrées obsolètes (présentes dans en.php, plus utilisées dans le
// code) sont déplacées en fin de fichier, en commentaire - à supprimer à
// la main une fois vérifié qu'elles ne servent vraiment plus à rien.
// code) sont déplacées en fin de fichier, dans un bloc de commentaire.
// Ce bloc est RELU à chaque passage : le script n'y fait qu'AJOUTER les
// entrées nouvellement obsolètes. Une entrée n'en sort que si vous la
// supprimez à la main, ou si son texte est de nouveau utilisé dans le code
// (elle retrouve alors sa traduction).
// - Les entrées sont triées par ordre alphabétique du texte français
// (insensible à la casse et aux accents), pour que deux textes presque
// identiques (typiquement une coquille) se retrouvent l'un à côté de l'autre.
// - Contrôle des espaces réservés : pour chaque traduction non vide, les jetons
// [[Nom]], [[Email]]... et les formats %s, %d... doivent être ceux du texte
// français (ils ne se traduisent pas). Simple avertissement à chaque passage,
// qui ne bloque rien. Les crochets simples ([Owner]...) ne sont PAS contrôlés :
// ce sont des libellés affichés, que l'on traduit.
// - Chaque entrée occupe deux lignes : le texte français, puis '=>' et la
// traduction sur la ligne suivante, précédé d'une tabulation.
//
// Limite connue et acceptée (même limite que qsTr()/gettext) : ne détecte
// que les chaînes littérales simples (pas de concaténation dynamique).
// Pour du texte avec contenu variable, utiliser un espace réservé :
// sprintf(t("Bonjour %s"), $nom)
// plutôt que t("Bonjour " . $nom) - la seconde forme ne sera pas détectée.
//
// Usage : php extraire_traductions.php [racine] (racine par défaut : /var/www)
$racine = "/var/www";
$racine = $argv[1] ?? "/var/www";
$fichier_en = "$racine/config/lang/en.php";
// ----------------------------------------------------------------------------
// Clé de tri : minuscules, sans accents, sans balises HTML, espaces réduits,
// sans ponctuation en tête. Sert UNIQUEMENT à ordonner : le texte réellement
// stocké dans en.php reste celui du code, inchangé.
// ----------------------------------------------------------------------------
function cle_de_tri($texte)
{
static $table = [
'à'=>'a','á'=>'a','â'=>'a','ä'=>'a','ã'=>'a','å'=>'a',
'À'=>'a','Á'=>'a','Â'=>'a','Ä'=>'a','Ã'=>'a','Å'=>'a',
'ç'=>'c','Ç'=>'c',
'è'=>'e','é'=>'e','ê'=>'e','ë'=>'e',
'È'=>'e','É'=>'e','Ê'=>'e','Ë'=>'e',
'ì'=>'i','í'=>'i','î'=>'i','ï'=>'i',
'Ì'=>'i','Í'=>'i','Î'=>'i','Ï'=>'i',
'ñ'=>'n','Ñ'=>'n',
'ò'=>'o','ó'=>'o','ô'=>'o','ö'=>'o','õ'=>'o',
'Ò'=>'o','Ó'=>'o','Ô'=>'o','Ö'=>'o','Õ'=>'o',
'ù'=>'u','ú'=>'u','û'=>'u','ü'=>'u',
'Ù'=>'u','Ú'=>'u','Û'=>'u','Ü'=>'u',
'ý'=>'y','ÿ'=>'y','Ý'=>'y',
'œ'=>'oe','Œ'=>'oe','æ'=>'ae','Æ'=>'ae',
'’'=>"'", '‘'=>"'", "\u{00A0}"=>' ',
];
$cle = strtr($texte, $table);
$cle = strtolower($cle); // ASCII seulement : les accents sont déjà traités
$cle = strip_tags($cle);
$cle = preg_replace('/\s+/', ' ', $cle) ?? $cle;
$cle = trim($cle);
$cle = preg_replace('/^[^\p{L}\p{N}]+/u', '', $cle) ?? $cle;
return $cle;
}
// Tri par texte français ; à clé égale, ordre des octets pour rester déterministe.
function trier_par_texte(array $tableau)
{
$cles = [];
foreach ($tableau as $texte => $_) $cles[$texte] = cle_de_tri((string)$texte);
uksort($tableau, function ($a, $b) use ($cles) {
$c = strcmp($cles[$a], $cles[$b]);
return $c !== 0 ? $c : strcmp((string)$a, (string)$b);
});
return $tableau;
}
// Une entrée sur deux lignes : le texte, puis TAB + '=> traduction,'
function formater_entree($cle, $valeur)
{
return " " . var_export($cle, true) . "\n\t=> " . var_export($valeur, true) . ",\n";
}
// ----------------------------------------------------------------------------
// Contrôle des espaces réservés (jetons [[...]] et formats de sprintf)
// ----------------------------------------------------------------------------
// Jetons [[Nom]] et formats %s, %d, %1$s, %.2f... trouvés dans un texte.
function extraire_jetons($texte)
{
preg_match_all('/\[\[[^\]\r\n]+\]\]/u', $texte, $m1);
$sans_pourcent_litteral = str_replace('%%', '', $texte); // '%%' = un vrai signe %
preg_match_all('/%(?:\d+\$)?[-+0]*\d*(?:\.\d+)?[bcdeEfFgGosuxX]/', $sans_pourcent_litteral, $m2);
return ['reserves' => $m1[0], 'formats' => $m2[0]];
}
// Compare deux listes en ignorant l'ordre. Renvoie [manquants, en trop].
function ecart_jetons(array $attendus, array $obtenus)
{
$manquants = []; $en_trop = [];
$ca = array_count_values($attendus);
$co = array_count_values($obtenus);
foreach ($ca as $j => $n) if (($co[$j] ?? 0) < $n) $manquants[] = $j;
foreach ($co as $j => $n) if (($ca[$j] ?? 0) < $n) $en_trop[] = $j;
return [$manquants, $en_trop];
}
// Renvoie la liste des problèmes (chaînes lisibles) entre un texte français et sa traduction.
function controler_jetons($francais, $traduction)
{
$problemes = [];
$fr = extraire_jetons($francais);
$en = extraire_jetons($traduction);
// 1) Jetons [[...]] : présence seulement, l'ordre est libre.
[$manquants, $en_trop] = ecart_jetons($fr['reserves'], $en['reserves']);
if ($manquants) $problemes[] = "jeton(s) absent(s), ou en nombre insuffisant, dans la traduction : " . implode(' ', $manquants);
if ($en_trop) $problemes[] = "jeton(s) inconnu(s) dans la traduction : " . implode(' ', $en_trop);
// 2) Formats sprintf : on compare le type de conversion (s, d, f...) sans
// la position ni la largeur, pour ne pas signaler un %1$s légitime.
$types = function ($liste) {
return array_map(function ($f) { return substr($f, -1); }, $liste);
};
$tf = $types($fr['formats']);
$te = $types($en['formats']);
[$manquants, $en_trop] = ecart_jetons($tf, $te);
if ($manquants) $problemes[] = "format(s) sprintf absent(s), ou en nombre insuffisant, dans la traduction : %" . implode(' %', $manquants);
if ($en_trop) $problemes[] = "format(s) sprintf en trop dans la traduction : %" . implode(' %', $en_trop);
// 3) Même formats mais dans un autre ordre : dangereux seulement s'ils ne
// sont pas numérotés (%s puis %d), les valeurs seraient alors échangées.
if (!$manquants && !$en_trop && $tf !== $te) {
$numerotes = false;
foreach ($en['formats'] as $f) if (strpos($f, '$') !== false) $numerotes = true;
if (!$numerotes) {
$problemes[] = "formats sprintf dans un autre ordre (" . implode(' ', $en['formats'])
. " au lieu de " . implode(' ', $fr['formats'])
. ") : numéroter avec %1\$s, %2\$s, ou remettre l'ordre du français";
}
}
return $problemes;
}
// Texte français raccourci pour l'affichage (coupé sur un caractère entier).
function abreger($texte, $max = 70)
{
$texte = trim(preg_replace('/\s+/', ' ', $texte));
if (preg_match('/^.{1,' . $max . '}/us', $texte, $m) && $m[0] !== $texte) return $m[0] . '…';
return $texte;
}
// ----------------------------------------------------------------------------
// Relit le bloc des obsolètes (commentaire en fin de fichier) et le renvoie
// sous forme de tableau texte => traduction. Renvoie [] s'il n'y en a pas.
// Lève une exception si le bloc a été modifié à la main de façon à ne plus
// être du PHP valide (par exemple une entrée dont il ne reste que la moitié).
// ----------------------------------------------------------------------------
function lire_obsoletes($fichier)
{
if (!file_exists($fichier)) return [];
$brut = file_get_contents($fichier);
if (!preg_match('/\/\*\n\/\/ OBSOLÈTES[^\n]*\n(.*?)\*\/\s*\z/su', $brut, $m)) return [];
// Les '*/' des textes ont été écrits '*\/' pour ne pas fermer le commentaire :
$entrees = str_replace('*\/', '*/', $m[1]);
$tmp = tempnam(sys_get_temp_dir(), 'obs');
file_put_contents($tmp, "<?php\nreturn [\n" . $entrees . "];\n");
try {
$tableau = include $tmp;
} finally {
unlink($tmp);
}
return is_array($tableau) ? $tableau : [];
}
// ----------------------------------------------------------------------------
// Recherche des appels t("...") dans le code
// ----------------------------------------------------------------------------
$chaines_trouvees = [];
$iterateur = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($racine));
foreach ($iterateur as $fichier) {
@@ -37,21 +200,52 @@ foreach ($iterateur as $fichier) {
$traductions_en = file_exists($fichier_en) ? include $fichier_en : [];
// --- Rapport console, comme avant ---
echo "=== Chaînes utilisées dans le code : " . count($chaines_trouvees) . " ===\n\n";
// Lecture du bloc des obsolètes AVANT toute écriture : si elle échoue, on
// s'arrête sans avoir rien modifié.
try {
$obsoletes_anciens = lire_obsoletes($fichier_en);
} catch (Throwable $e) {
fwrite(STDERR, "ERREUR : le bloc des entrées obsolètes de $fichier_en n'est plus du PHP valide.\n");
fwrite(STDERR, " (" . $e->getMessage() . ")\n");
fwrite(STDERR, "Chaque entrée doit rester sur deux lignes : 'texte' puis, à la ligne, '=> traduction,'.\n");
fwrite(STDERR, "Corrigez le bloc (ou supprimez-le) puis relancez. RIEN n'a été modifié.\n");
exit(1);
}
// --- Bilan ---
$nouvellement_obsoletes = [];
foreach (array_diff(array_keys($traductions_en), array_keys($chaines_trouvees)) as $chaine) {
$nouvellement_obsoletes[$chaine] = $traductions_en[$chaine];
}
// Un texte de nouveau utilisé dans le code, mais présent dans le bloc des
// obsolètes, retrouve sa traduction et sort du bloc :
$reactivees = [];
foreach ($chaines_trouvees as $chaine => $fichiers) {
if (!array_key_exists($chaine, $traductions_en) && array_key_exists($chaine, $obsoletes_anciens)) {
$reactivees[$chaine] = $obsoletes_anciens[$chaine];
unset($obsoletes_anciens[$chaine]);
}
}
$manquantes = [];
foreach ($chaines_trouvees as $chaine => $fichiers) {
if (!array_key_exists($chaine, $traductions_en)) {
if (!array_key_exists($chaine, $traductions_en) && !array_key_exists($chaine, $reactivees)) {
$manquantes[$chaine] = $fichiers[0];
}
}
$obsoletes_total = $obsoletes_anciens + $nouvellement_obsoletes;
echo "=== Chaînes utilisées dans le code : " . count($chaines_trouvees) . " ===\n\n";
echo count($manquantes) . " nouvelle(s) chaîne(s) à traduire.\n";
echo count($nouvellement_obsoletes) . " entrée(s) devenue(s) obsolète(s), ajoutée(s) au bloc en commentaire.\n";
echo count($obsoletes_anciens) . " entrée(s) obsolète(s) déjà en commentaire, conservée(s) (à supprimer à la main).\n";
echo count($reactivees) . " entrée(s) obsolète(s) de nouveau utilisée(s), traduction récupérée.\n\n";
$obsoletes = array_diff(array_keys($traductions_en), array_keys($chaines_trouvees));
echo count($obsoletes) . " entrée(s) obsolète(s), déplacée(s) en commentaire.\n\n";
// --- Régénération de lang/en.php ---
// ----------------------------------------------------------------------------
// Régénération de lang/en.php
// ----------------------------------------------------------------------------
// Sauvegarde de précaution avant d'écraser :
if (file_exists($fichier_en)) {
@@ -59,31 +253,61 @@ if (file_exists($fichier_en)) {
}
// Fusion : traductions existantes conservées pour les chaînes encore utilisées,
// nouvelles chaînes ajoutées avec une valeur vide :
// traductions récupérées du bloc des obsolètes, nouvelles chaînes vides :
$nouveau_tableau = [];
foreach ($chaines_trouvees as $chaine => $fichiers) {
$nouveau_tableau[$chaine] = $traductions_en[$chaine] ?? "";
$nouveau_tableau[$chaine] = $traductions_en[$chaine] ?? $reactivees[$chaine] ?? "";
}
$nouveau_tableau = trier_par_texte($nouveau_tableau);
$contenu = "<?php\n";
$contenu .= "// Fichier régénéré automatiquement par extraire_traductions.php\n";
$contenu .= "// le " . date("d/m/Y à H:i:s") . " - les valeurs vides sont à traduire manuellement.\n";
$contenu .= "// Entrées triées par ordre alphabétique du texte français.\n";
$contenu .= "return [\n";
foreach ($nouveau_tableau as $cle => $valeur) {
$contenu .= " " . var_export($cle, true) . " => " . var_export($valeur, true) . ",\n";
$contenu .= formater_entree($cle, $valeur);
}
$contenu .= "];\n";
if (!empty($obsoletes)) {
$contenu .= "\n/*\n";
$contenu .= "// OBSOLÈTES au " . date("d/m/Y à H:i:s") . " - plus utilisées dans le code.\n";
$contenu .= "// À supprimer manuellement une fois confirmé qu'elles ne servent vraiment plus.\n";
foreach ($obsoletes as $chaine) {
$contenu .= var_export($chaine, true) . " => " . var_export($traductions_en[$chaine], true) . ",\n";
if (!empty($obsoletes_total)) {
$bloc = "";
foreach (trier_par_texte($obsoletes_total) as $cle => $valeur) {
$bloc .= formater_entree($cle, $valeur);
}
// Une séquence '*/' dans un texte fermerait le commentaire trop tôt :
$bloc = str_replace('*/', '*\/', $bloc);
$contenu .= "\n/*\n";
$contenu .= "// OBSOLÈTES (bloc mis à jour le " . date("d/m/Y à H:i:s") . ") - plus utilisées dans le code.\n";
$contenu .= "// Conservées d'un passage à l'autre : à supprimer à la main quand elles ne servent vraiment plus.\n";
$contenu .= $bloc;
$contenu .= "*/\n";
}
file_put_contents($fichier_en, $contenu);
echo "Fichier régénéré : $fichier_en\n";
echo "(sauvegarde de l'ancienne version : $fichier_en.bak)\n";
// ----------------------------------------------------------------------------
// Contrôle des espaces réservés (affiché à CHAQUE passage tant que ce n'est
// pas corrigé ; ne bloque rien)
// ----------------------------------------------------------------------------
$avertissements = [];
foreach ($nouveau_tableau as $francais => $traduction) {
if ($traduction === "") continue; // pas encore traduit
$problemes = controler_jetons((string)$francais, (string)$traduction);
if ($problemes) $avertissements[$francais] = $problemes;
}
echo "\n";
if (empty($avertissements)) {
echo "Espaces réservés ([[...]], %s...) : cohérents dans toutes les traductions.\n";
} else {
echo "ATTENTION : " . count($avertissements) . " traduction(s) dont les espaces réservés diffèrent du français.\n";
echo "Ils ne se traduisent pas : le code cherche les jetons exacts.\n";
foreach ($avertissements as $francais => $problemes) {
echo "\n « " . abreger((string)$francais) . " »\n";
foreach ($problemes as $probleme) echo " - $probleme\n";
}
}