diff --git a/ajax_bib_range.php b/ajax_bib_range.php
index c50cd47..ec28be4 100644
--- a/ajax_bib_range.php
+++ b/ajax_bib_range.php
@@ -10,6 +10,10 @@ require_once('php/inc_fonctions.php');
require_once('php/inc_fx_promoteur.php');
$strLangue = $_SESSION['lang'] ?? 'fr';
+$reqLang = $_REQUEST['lang'] ?? $_REQUEST['lng'] ?? '';
+if (!empty($reqLang) && in_array($reqLang, ['fr', 'en'], true)) {
+ $strLangue = $reqLang;
+}
$vPage = 'compte.php';
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
@@ -54,7 +58,7 @@ if ($action == 'create') {
echo json_encode([
'success' => false,
- 'message' => 'epr_id invalide'
+ 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
@@ -80,7 +84,7 @@ if ($action == 'delete') {
if (!$range) {
echo json_encode([
'success' => false,
- 'message' => 'Range introuvable'
+ 'message' => fxBibMsg('bib_v4_ajax_range_not_found')
]);
exit;
}
@@ -88,7 +92,7 @@ if ($action == 'delete') {
if ((int)$range[1]['nb_utilises'] > 0) {
echo json_encode([
'success' => false,
- 'message' => 'Impossible de supprimer : des dossards sont déjà utilisés dans ce range'
+ 'message' => fxBibMsg('bib_v4_ajax_delete_used')
]);
exit;
}
@@ -114,7 +118,7 @@ if ($action == 'delete') {
echo json_encode([
'success' => false,
- 'message' => 'id invalide'
+ 'message' => fxBibMsg('bib_v4_ajax_id_invalid')
]);
exit;
}
@@ -134,7 +138,7 @@ if ($action == 'save') {
// VALIDATION 1 — epr_id
// =====================
if ($is_new && $epr_id <= 0) {
- echo json_encode(['success' => false, 'message' => 'epr_id invalide']);
+ echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')]);
exit;
}
@@ -148,7 +152,7 @@ if ($action == 'save') {
// VALIDATION 2 — VALEURS NUMÉRIQUES
// =====================
if ($start <= 0 || $end <= 0) {
- echo json_encode(['success' => false, 'message' => 'Valeurs invalides']);
+ echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_values_invalid')]);
exit;
}
@@ -156,7 +160,7 @@ if ($action == 'save') {
// VALIDATION 3 — LOGIQUE INTERVALLE
// =====================
if ($start > $end) {
- echo json_encode(['success' => false, 'message' => 'Début doit être < fin']);
+ echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_start_gt_end')]);
exit;
}
@@ -169,7 +173,7 @@ if ($action == 'save') {
$epr_id = (int)$objDatabase->fxGetVar($sqlGet);
if ($epr_id <= 0) {
- echo json_encode(['success' => false, 'message' => 'Range introuvable']);
+ echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_range_not_found')]);
exit;
}
@@ -191,7 +195,7 @@ if ($action == 'save') {
if ($nbOrphans > 0) {
echo json_encode([
'success' => false,
- 'message' => 'Modification impossible : des dossards sortiraient du range'
+ 'message' => fxBibMsg('bib_v4_ajax_orphans')
]);
exit;
}
@@ -218,7 +222,7 @@ if ($action == 'save') {
if ($hasOverlap > 0) {
echo json_encode([
'success' => false,
- 'message' => 'Conflit avec un autre intervalle de dossards'
+ 'message' => fxBibMsg('bib_v4_ajax_overlap')
]);
exit;
}
@@ -272,14 +276,13 @@ if ($action == 'view') {
if ($epr_id <= 0 || $range_id < 0) {
echo json_encode([
'success' => false,
- 'message' => 'Paramètres invalides'
+ 'message' => fxBibMsg('bib_v4_ajax_params_invalid')
]);
exit;
}
// TEMPORAIRE : affichage simple pour test
- ob_start();
- $html = renderBibView($epr_id, $range_id);
+ $html = renderBibView($epr_id, $range_id, $strLangue);
echo json_encode([
'success' => true,
@@ -299,7 +302,7 @@ if ($action == 'batch_analysis') {
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
- 'message' => 'epr_id invalide'
+ 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
@@ -319,7 +322,7 @@ if ($action == 'refresh_ranges') {
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
- 'message' => 'epr_id invalide'
+ 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
@@ -406,7 +409,12 @@ if ($action == 'batch_go') {
$simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation');
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id);
- $info = "{$summary['nb_dispo']} disponibles / {$summary['nb_participants']} à assigner → manque {$summary['nb_manque']}";
+ $info = fxBibMsg(
+ 'bib_v4_ajax_sim_summary',
+ $summary['nb_dispo'],
+ $summary['nb_participants'],
+ $summary['nb_manque']
+ );
// =====================
// SIMULATION (temporaire simple)
// =====================
@@ -418,10 +426,10 @@ if ($action == 'batch_go') {
$html = renderBibView(
$epr_id,
0,
- 'fr',
+ $strLangue,
'simulation',
$simu,
- 'Résultats de la simulation',
+ fxBibTexte('bib_v4_ajax_title_simulation'),
$info
);
@@ -475,23 +483,23 @@ if ($action == 'batch_apply') {
}
if ($assigned === 0) {
- $info = "Aucun dossard n’a pu être assigné.";
+ $info = fxBibMsg('bib_v4_ajax_assign_none');
} elseif ($assigned === $total) {
- $info = "Assignation complétée : $assigned dossards assignés avec succès.";
+ $info = fxBibMsg('bib_v4_ajax_assign_full', $assigned);
} else {
- $info = "Assignation partielle complétée : $assigned dossards assignés avec succès. $remaining participants restent à assigner.";
+ $info = fxBibMsg('bib_v4_ajax_assign_partial', $assigned, $remaining);
}
$html = renderBibView(
$epr_id,
0,
- 'fr',
+ $strLangue,
'execute',
$exec,
- 'Résultats de l’assignation',
+ fxBibTexte('bib_v4_ajax_title_assign'),
$info,$status
);
@@ -516,11 +524,11 @@ if ($action == 'reset_preview') {
$html = renderBibView(
$epr_id,
0,
- 'fr',
+ $strLangue,
'reset',
$participants,
- 'Prévisualisation de la réinitialisation',
- count($participants) . ' dossards seront retirés',
+ fxBibTexte('bib_v4_ajax_title_reset_preview'),
+ fxBibMsg('bib_v4_ajax_reset_preview_count', count($participants)),
'warning'
);
@@ -567,11 +575,11 @@ if ($action == 'reset_apply') {
$html = renderBibView(
$epr_id,
0,
- 'fr',
+ $strLangue,
'reset',
$participants,
- 'Réinitialisation terminée',
- $count . ' dossards retirés',
+ fxBibTexte('bib_v4_ajax_title_reset_done'),
+ fxBibMsg('bib_v4_ajax_reset_done_count', $count),
'success'
);
@@ -594,7 +602,7 @@ if ($action == 'save_team_mode') {
echo json_encode([
'success' => false,
- 'message' => 'Paramètres invalides'
+ 'message' => fxBibMsg('bib_v4_ajax_params_invalid')
]);
exit;
@@ -625,7 +633,7 @@ if ($action == 'save_auto_config') {
if (!fxBibRequirePromoteurSession()) {
echo json_encode([
'success' => false,
- 'message' => 'Session invalide'
+ 'message' => fxBibMsg('bib_v4_ajax_session_invalid')
]);
exit;
}
@@ -633,7 +641,7 @@ if ($action == 'save_auto_config') {
if (!fxBibValidateCsrf($_POST['csrf_token'] ?? '')) {
echo json_encode([
'success' => false,
- 'message' => 'Requête non autorisée'
+ 'message' => fxBibMsg('bib_v4_ajax_unauthorized')
]);
exit;
}
@@ -645,7 +653,7 @@ if ($action == 'save_auto_config') {
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
- 'message' => 'epr_id invalide'
+ 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
@@ -655,7 +663,7 @@ if ($action == 'save_auto_config') {
if (!$result['success']) {
echo json_encode([
'success' => false,
- 'message' => $result['message'] ?? 'Erreur'
+ 'message' => $result['message'] ?? fxBibMsg('bib_v4_ajax_error')
]);
exit;
}
@@ -675,6 +683,6 @@ if ($action == 'save_auto_config') {
// =====================
echo json_encode([
'success' => false,
- 'message' => 'Action invalide'
+ 'message' => fxBibMsg('bib_v4_ajax_action_invalid')
]);
exit;
\ No newline at end of file
diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php
index fa53392..10984bb 100644
--- a/inc_footer_scripts.php
+++ b/inc_footer_scripts.php
@@ -1966,6 +1966,11 @@ if ($strLangue == 'fr') {
if (moduleBib) {
+ function bibAjaxLangSuffix() {
+ var lang = moduleBib.dataset.lang || 'fr';
+ return '&lang=' + encodeURIComponent(lang);
+ }
+
// MSIN-4379 — Réduire / développer les blocs gestion et assignation (en-tête seul visible).
moduleBib.addEventListener('click', function (e) {
let btnToggle = e.target.closest('.epr-block-toggle');
@@ -2197,6 +2202,7 @@ if ($strLangue == 'fr') {
'action=refresh_ranges'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&mode=assign'
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -2238,6 +2244,7 @@ if ($strLangue == 'fr') {
'action=refresh_ranges'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&mode=auto'
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -2334,7 +2341,7 @@ if ($strLangue == 'fr') {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
- body: 'action=batch_analysis&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ body: 'action=batch_analysis&epr_id=' + encodeURIComponent(row.dataset.eprId) + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -2412,6 +2419,7 @@ if ($strLangue == 'fr') {
'action=save_team_mode'
+ '&epr_id=' + encodeURIComponent(box.dataset.eprId)
+ '&team_mode=' + encodeURIComponent(teamRadio.value)
+ + bibAjaxLangSuffix()
});
return;
@@ -2455,6 +2463,7 @@ if ($strLangue == 'fr') {
'action=refresh_ranges'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&mode=' + encodeURIComponent(row.dataset.mode || 'assign')
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -2554,7 +2563,7 @@ if ($strLangue == 'fr') {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
- body: 'action=delete&id=' + encodeURIComponent(id)
+ body: 'action=delete&id=' + encodeURIComponent(id) + bibAjaxLangSuffix()
})
.then(res => res.text())
.then(raw => {
@@ -2629,6 +2638,7 @@ if ($strLangue == 'fr') {
+ '&epr_id=' + encodeURIComponent(epr_id)
+ '&start=' + encodeURIComponent(start)
+ '&end=' + encodeURIComponent(end)
+ + bibAjaxLangSuffix()
})
.then(res => res.text())
.then(raw => {
@@ -2695,6 +2705,7 @@ if ($strLangue == 'fr') {
'action=view'
+ '&epr_id=' + encodeURIComponent(epr_id)
+ '&range_id=' + encodeURIComponent(range_id)
+ + bibAjaxLangSuffix()
})
.then(res => res.text())
.then(raw => {
@@ -2852,6 +2863,7 @@ if ($strLangue == 'fr') {
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ '&ba_id=' + encodeURIComponent(ba_id)
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -2952,6 +2964,7 @@ if ($strLangue == 'fr') {
'action=refresh_ranges'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&mode=reset'
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -3015,6 +3028,7 @@ if ($strLangue == 'fr') {
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ '&ba_id=' + encodeURIComponent(ba_id)
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -3074,6 +3088,7 @@ if ($strLangue == 'fr') {
'action=refresh_ranges'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&mode=assign'
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(refresh => {
@@ -3090,6 +3105,7 @@ if ($strLangue == 'fr') {
body:
'action=batch_analysis'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(stats => {
@@ -3145,6 +3161,7 @@ if ($strLangue == 'fr') {
+ '&active=1'
+ '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ '&csrf_token=' + encodeURIComponent(bibCsrfToken())
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -3198,6 +3215,7 @@ if ($strLangue == 'fr') {
+ '&active=0'
+ '&ranges=' + encodeURIComponent('[]')
+ '&csrf_token=' + encodeURIComponent(bibCsrfToken())
+ + bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
@@ -3257,7 +3275,7 @@ if ($strLangue == 'fr') {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
- body: 'action=create&epr_id=' + encodeURIComponent(epr_id)
+ body: 'action=create&epr_id=' + encodeURIComponent(epr_id) + bibAjaxLangSuffix()
})
.then(res => res.text())
.then(raw => {
diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php
index c71d76f..64a529b 100644
--- a/php/inc_fx_promoteur.php
+++ b/php/inc_fx_promoteur.php
@@ -2766,6 +2766,15 @@ function fxBibTexte($clef, $mem_echo = 0, $strFallback = '', $blnAllowEmptyLabel
return $str;
}
+/** Message bib v4 avec placeholders sprintf (%d, %s…) — AJAX et vues dynamiques. */
+function fxBibMsg($clef, ...$args) {
+ $str = fxBibTexte($clef, 0);
+ if (!empty($args)) {
+ return vsprintf($str, $args);
+ }
+ return $str;
+}
+
/** Texte d'aide long bib v4 — même règle que fxBibTexte pour l'auto-création. */
function fxBibAide($clef, $mem_echo = 0) {
$row = fxBibGetInfoRow($clef);
@@ -3199,7 +3208,7 @@ function fxShowBibTool4($str_code, $int_eve_id, $strLangue){
>
+
>
@@ -3530,7 +3539,7 @@ function fxSaveBibAutoConfig($epr_id, $ranges, $active = true) {
$active = (bool)$active;
if ($epr_id <= 0) {
- return ['success' => false, 'message' => 'epr_id invalide'];
+ return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')];
}
$objDatabase->fxQuery("
@@ -3544,13 +3553,13 @@ function fxSaveBibAutoConfig($epr_id, $ranges, $active = true) {
}
if (empty($ranges) || !is_array($ranges)) {
- return ['success' => false, 'message' => 'Aucune séquence sélectionnée'];
+ return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_no_seq_selected')];
}
$ids = array_values(array_filter(array_map('intval', $ranges)));
if (empty($ids)) {
- return ['success' => false, 'message' => 'Aucune séquence sélectionnée'];
+ return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_no_seq_selected')];
}
$strIds = implode(',', $ids);
@@ -3564,7 +3573,7 @@ function fxSaveBibAutoConfig($epr_id, $ranges, $active = true) {
$nbValid = (int)$objDatabase->fxGetVar($sqlCheck);
if ($nbValid !== count($ids)) {
- return ['success' => false, 'message' => 'Séquences invalides pour cette épreuve'];
+ return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_seq_invalid')];
}
$objDatabase->fxQuery("
diff --git a/sql/MSIN-4379-bib_v4-etape4-ajax.sql b/sql/MSIN-4379-bib_v4-etape4-ajax.sql
new file mode 100644
index 0000000..78befff
--- /dev/null
+++ b/sql/MSIN-4379-bib_v4-etape4-ajax.sql
@@ -0,0 +1,181 @@
+-- MSIN-4379 — Étape 4 / i18n bib v4 — messages AJAX (ajax_bib_range.php)
+-- Aucun DELETE. N'écrase pas les entrées existantes.
+-- %d = entiers dans les messages dynamiques (sprintf côté PHP).
+
+-- Erreurs générales
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_epr_invalid', 'fr', 'Identifiant d''épreuve invalide', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_epr_invalid' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_epr_invalid', 'en', 'Invalid race id', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_epr_invalid' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_id_invalid', 'fr', 'Identifiant invalide', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_id_invalid' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_id_invalid', 'en', 'Invalid id', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_id_invalid' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_range_not_found', 'fr', 'Séquence introuvable', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_range_not_found' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_range_not_found', 'en', 'Sequence not found', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_range_not_found' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_delete_used', 'fr', 'Impossible de supprimer : des dossards sont déjà utilisés dans cette séquence', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_delete_used' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_delete_used', 'en', 'Cannot delete: bibs are already in use in this sequence', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_delete_used' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_values_invalid', 'fr', 'Valeurs invalides', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_values_invalid' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_values_invalid', 'en', 'Invalid values', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_values_invalid' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_start_gt_end', 'fr', 'Le début doit être inférieur à la fin', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_start_gt_end' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_start_gt_end', 'en', 'Start must be less than end', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_start_gt_end' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_orphans', 'fr', 'Modification impossible : des dossards sortiraient de la séquence', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_orphans' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_orphans', 'en', 'Cannot update: some bibs would fall outside the sequence', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_orphans' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_overlap', 'fr', 'Conflit avec une autre séquence de dossards', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_overlap' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_overlap', 'en', 'Conflict with another bib sequence', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_overlap' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_params_invalid', 'fr', 'Paramètres invalides', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_params_invalid' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_params_invalid', 'en', 'Invalid parameters', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_params_invalid' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_session_invalid', 'fr', 'Session invalide', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_session_invalid' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_session_invalid', 'en', 'Invalid session', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_session_invalid' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_unauthorized', 'fr', 'Requête non autorisée', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_unauthorized' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_unauthorized', 'en', 'Unauthorized request', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_unauthorized' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_error', 'fr', 'Erreur', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_error' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_error', 'en', 'Error', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_error' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_action_invalid', 'fr', 'Action invalide', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_action_invalid' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_action_invalid', 'en', 'Invalid action', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_action_invalid' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_no_seq_selected', 'fr', 'Aucune séquence sélectionnée', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_no_seq_selected' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_no_seq_selected', 'en', 'No sequence selected', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_no_seq_selected' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_seq_invalid', 'fr', 'Séquences invalides pour cette épreuve', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_seq_invalid' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_seq_invalid', 'en', 'Invalid sequences for this race', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_seq_invalid' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+-- Titres vues batch / reset
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_simulation', 'fr', 'Résultats de la simulation', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_simulation' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_simulation', 'en', 'Simulation results', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_simulation' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_assign', 'fr', 'Résultats de l''assignation', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_assign' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_assign', 'en', 'Assignment results', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_assign' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_reset_preview', 'fr', 'Prévisualisation de la réinitialisation', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_reset_preview' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_reset_preview', 'en', 'Reset preview', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_reset_preview' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_reset_done', 'fr', 'Réinitialisation terminée', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_reset_done' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_title_reset_done', 'en', 'Reset completed', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_reset_done' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+-- Messages dynamiques (%d)
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_sim_summary', 'fr', '%d disponibles / %d à assigner → manque %d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_sim_summary' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_sim_summary', 'en', '%d available / %d to assign → short %d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_sim_summary' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_assign_none', 'fr', 'Aucun dossard n''a pu être assigné.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_assign_none' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_assign_none', 'en', 'No bib could be assigned.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_assign_none' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_assign_full', 'fr', 'Assignation complétée : %d dossards assignés avec succès.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_assign_full' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_assign_full', 'en', 'Assignment complete: %d bibs assigned successfully.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_assign_full' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_assign_partial', 'fr', 'Assignation partielle : %d dossards assignés. %d participants restent à assigner.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_assign_partial' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_assign_partial', 'en', 'Partial assignment: %d bibs assigned. %d participants still unassigned.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_assign_partial' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_reset_preview_count', 'fr', '%d dossards seront retirés', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_reset_preview_count' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_reset_preview_count', 'en', '%d bibs will be removed', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_reset_preview_count' AND info_langue = 'en' AND info_prg = 'compte.php');
+
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_reset_done_count', 'fr', '%d dossards retirés', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_reset_done_count' AND info_langue = 'fr' AND info_prg = 'compte.php');
+INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
+SELECT 'bib_v4_ajax_reset_done_count', 'en', '%d bibs removed', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_reset_done_count' AND info_langue = 'en' AND info_prg = 'compte.php');