diff --git a/inc/stats_trads_to_array.php b/inc/stats_trads_to_array.php
index 41e5f719f04cd5bc17cafa30698bf6d62ae65845..b390836680bafa7b13516b5c42c42854697ee0be 100644
--- a/inc/stats_trads_to_array.php
+++ b/inc/stats_trads_to_array.php
@@ -5,87 +5,146 @@ if (!defined('_ECRIRE_INC_VERSION')) {
 }
 
 include_spip('inc/statistiques');
-// moyenne glissante sur 30 jours
-define('MOYENNE_GLISSANTE_JOUR', 30);
-// moyenne glissante sur 12 mois
-define('MOYENNE_GLISSANTE_MOIS', 12);
 
-function inc_stats_trads_to_array_dist($unite, $duree, $id_tradlang_module, $options = []) {
-	$now = time();
 
-	if (!in_array($unite, ['jour', 'mois'])) {
-		$unite = 'jour';
+include_spip('inc/statistiques');
+
+/**
+ * Retourne les statistiques de versionnage des traductions pour une durée donnée
+ *
+ * @param string $unite jour | mois | annee
+ * @param ?int $duree Combien de jours | mois | annee on prend…
+ * @param ?string $objet (unused)
+ * @param ?int $id_objet (id_auteur)
+ * @return array [date => nb visites]
+ */
+function inc_stats_trads_to_array_dist($unite, ?int $duree = null, ?string $objet = null, ?int $id_objet = null) {
+
+	$unites = [
+		'jour' => 'day',
+		'day' => 'day',
+		'semaine' => 'week',
+		'week' => 'week',
+		'mois' => 'month',
+		'month' => 'month',
+		'annee' => 'year',
+		'year' => 'year',
+	];
+	$unite = $unites[$unite] ?? 'day';
+	$period_unit = $unite;
+	$period_duration = $duree;
+
+	switch ($unite) {
+		case 'day':
+			$format_sql = '%Y-%m-%d';
+			$format = 'Y-m-d';
+			$period_unit_interval = 'D';
+			break;
+
+		case 'week':
+			// https://en.wikipedia.org/wiki/ISO_week_date
+			$format_sql = '%x-W%v';
+			$format = 'o-\WW';
+			$n_today = (new \DateTime())->format('w'); // dimanche 0, samedi 6
+			// on se cale sur un lundi
+			$period_duration = 7 * $duree - $n_today;
+			$period_unit = 'day';
+			$period_unit_interval = 'D';
+			break;
+
+		case 'month':
+			$format_sql = '%Y-%m';
+			$format = 'Y-m';
+			$period_unit_interval = 'M';
+			break;
+
+		case 'year':
+			$format_sql = '%Y';
+			$format = 'Y';
+			$period_unit_interval = 'Y';
+			break;
+
+		default:
+			throw new \RuntimeException("Invalid unit $unite");
 	}
-	$serveur = '';
 
+	if ($duree and $duree < 0) {
+		$duree = null;
+	}
+
+	$serveur = '';
 	$table = 'spip_versions';
 	$order = 'date';
-	$where = ['objet = "tradlang"', 'id_version > 0'];
 
+	$objet = 'tradlang';
+	$id_auteur = $id_objet;
 
-	if (!isset($options['id_auteur']) || !is_numeric($options['id_auteur'])) {
+	$where = [
+		'objet = ' . sql_quote($objet),
+		'id_version > 0'
+	];
+	if (!$id_auteur) {
 		$where[] = 'id_auteur > 0';
 	} else {
-		$where[] = 'id_auteur = ' . (int) $options['id_auteur'];
+		$where[] = 'id_auteur = ' . $id_auteur;
 	}
 
+	$currentDate = (new \DateTime())->format($format);
+	$startDate = null;
+	$endDate = $currentDate;
+
 	if ($duree) {
-		$where[] = sql_date_proche($order, -$duree, 'day', $serveur);
+		$where[] = sql_date_proche($order, -$period_duration, $period_unit, $serveur);
+		// sql_date_proche utilise une comparaison stricte. On soustrait 1 jour...
+		$startDate = (new \DateTime())->sub(new \DateInterval('P' . ($period_duration - 1) . $period_unit_interval))->format($format);
 	}
 
 	$where = implode(' AND ', $where);
-	$format = ($unite == 'jour' ? '%Y-%m-%d' : '%Y-%m-01');
-	$res = sql_select("COUNT(*) AS v, DATE_FORMAT($order,'$format') AS d", $table, $where, 'd', 'd', '', '', $serveur);
-	$format = str_replace('%', '', $format);
-	$periode = ($unite == 'jour' ? 24 * 3600 : 365 * 24 * 3600 / 12);
-	$step = (int) round($periode * 1.1, 0);
-	$glisse = constant('MOYENNE_GLISSANTE_' . strtoupper($unite));
-	moyenne_glissante();
-	$data = [];
-	$r = sql_fetch($res, $serveur);
-	if (!$r) {
-		$r = ['d' => date($format, $now), 'v' => 0];
+
+	$firstDateStat = sql_getfetsel('date', $table, $where, '', 'date', '0,1');
+	if ($firstDateStat) {
+		$firstDate = (new \DateTime($firstDateStat))->format($format);
+	} else {
+		$firstDate = null;
 	}
-	do {
-		$data[$r['d']] = ['versions' => $r['v'], 'moyenne' => moyenne_glissante($r['v'], $glisse)];
-		$last = $r['d'];
-
-		// donnee suivante
-		$r = sql_fetch($res, $serveur);
-		// si la derniere n'est pas la date courante, l'ajouter
-		if (!$r && $last != date($format, $now)) {
-			$r = ['d' => date($format, $now), 'v' => 0];
-		}
-
-		// completer les trous manquants si besoin
-		if ($r) {
-			$next = strtotime($last);
-			$current = strtotime($r['d']);
-			while (($next += $step) < $current && ($d = date($format, $next))) {
-				if (!isset($data[$d])) {
-					$data[$d] = ['versions' => 0, 'moyenne' => moyenne_glissante(0, $glisse)];
-				}
-				$last = $d;
-				$next = strtotime($last);
-			}
-		}
-	} while ($r);
-
-	// projection pour la derniere barre :
-	// mesure courante
-	// + moyenne au pro rata du temps qui reste
-	$moyenne = end($data);
-	$moyenne = prev($data);
-	$moyenne = ($moyenne && isset($moyenne['moyenne'])) ? $moyenne['moyenne'] : 0;
-	$data[$last]['moyenne'] = $moyenne;
-
-	// temps restant
-	$remaining = strtotime(date($format, strtotime(date($format, $now)) + $step)) - $now;
-
-	$prorata = $remaining / $periode;
-
-	// projection
-	$data[$last]['prevision'] = $data[$last]['versions'] + (int) round($moyenne * $prorata);
-
-	return $data;
+
+	$data = sql_allfetsel(
+		"DATE_FORMAT($order, '$format_sql') AS formatted_date, COUNT(*) AS visites",
+		$table,
+		$where,
+		'formatted_date',
+		'formatted_date',
+		'',
+		'',
+		$serveur
+	);
+
+	$data = array_map(function ($d) {
+		$d['date'] = $d['formatted_date'];
+		unset($d['formatted_date']);
+		return $d;
+	}, $data);
+
+	return [
+		'meta' => [
+			'unite' => $unite,
+			'duree' => $duree,
+			'id_auteur' => $id_auteur,
+			'format_date' => $format,
+			'start_date' => $startDate ?? $firstDate ?? $endDate,
+			'end_date' => $endDate,
+			'first_date' => $firstDate,
+			'columns' => [
+				'date',
+				'visites',
+			],
+			'translations' => [
+				'date' => _T('public:date'),
+				'visites' => label_nettoyer(_T('tradlang:info_revisions_stats')),
+				'moyenne' => _T('statistiques:moyenne'),
+			],
+		],
+		'data' => array_values($data),
+	];
 }
+