Ces codes ont servi à générer les statistiques présentes dans MediaWiki:Gadget-translation editor.js/Statistiques.
Le principe pour générer les stats est simple :
La première partie a été réalisée via un code écrit en C++ (parce qu’inspirée d'un script de Pamputt), tandis que la 2e a été implémentée en Python, via le framework Pywikibot.
#include <iostream>
#include <fstream>
#include <string>
#include <map>
using namespace std;
// Dump à télécharger à : https://dumps.wikimedia.org/frwiktionary/latest/ :
// frwiktionary-latest-pages-meta-history.xml.7z , puis à décompresser
string input_folder = "C:\\Users\\automatik\\Documents\\Wiktionnaire\\Stats translation editor\\";
string input_file_history = input_folder + "frwiktionary-latest-pages-meta-history.xml";
// Chemins des fichiers contenant les résultats
string output_folder = "C:\\Users\\automatik\\Documents\\Wiktionnaire\\Stats translation editor\\";
string output_file_res = output_folder + "stats-summary.txt"; // Chiffres-clés
string output_file_langs = output_folder + "stats-langs.txt"; // Stats par langue
string output_file_dates = output_folder + "stats-months.txt"; // Stats par mois
string output_file_contribs = output_folder + "stats-trads-contributors.txt"; // Stats par contributeur
string output_file_ips = output_folder + "stats-trads-ips.txt"; // Stats par IP
string output_file_diffs = output_folder + "stats-trads-diffs-to-check.txt"; // Diffs à analyser en Partie 2
string from_date = "2014-07"; // Mois à partir duquel on dénombre les ajouts (mois inclus)
string to_date = "2024-01"; // Mois jusqu’auquel on dénombre les ajouts (mois exclus)
/**** STATS ****/
std::map<string, int> stats_langs = {}; // exemple (en début d’analyse) : { "italien": 3, "allemand" : 8 }
std::map<string, int> stats_dates = {}; // ex : { "2014-08": 6169, "2014-09" : 6203 }
std::map<string, int> stats_contribs = {}; // ex : { "Un utilisateur": 213, "Un deuxième utilisateur": 65 }
std::map<string, int> stats_ips = {}; // ex : { "184.252.23.16": 11, "174.145.64.87": 75 }
// Maps pour stocker temporairement des résultats (pour être sûr que les trads analysées n'ont pas été révoquées)
// Ces maps sont transférées vers les "map" finaux ci-dessus une fois qu'on est sûr que les traductions concernées n'ont pas été révoquées
// Note : pour s'assurer que les ajouts ne sont pas révoqués, on lit tous les résumés d'édition qui succèdent à un ajout de traduction
// jusqu'à ce qu'on tombe sur une révocation ou autre chose qu'un ajout de traduction (via l'outil). Si l'ajout correspond à la dernière version
// de l'article, on le considère comme non révoqué
std::map<string, int> stats_langs_tmp = {};
std::map<string, int> stats_dates_tmp = {};
std::map<string, int> stats_contribs_tmp = {};
std::map<string, int> stats_ips_tmp = {};
unsigned int counter_pages = 0; // Nombre de pages parcourues dans le dump (simplement pour afficher la progression de la tâche)
unsigned int counter_additions = 0; // Nombre d'ajouts avec l'outil de traductions (ajouts révoqués ou non)
unsigned int counter_pages_with_trans = 0; // Nombre de pages avec au moins un ajout de traduction non révoqué
unsigned int counter_trans = 0; // Nombre de traductions ajoutées et non révoquées
unsigned int total_count_contribs = 0, total_count_ips = 0; // Nombre de traductions non révoquées ajoutées par des contributeurs enregistrés / non enregistrés
unsigned int total_count_contribs_tmp = 0, total_count_ips_tmp = 0; // Dénombrements temporaires transférés si traductions pas révoquées
unsigned int counter_additions_contributors = 0; // Nombre d’ajouts de traduction(s) par des utilisateurs… inscrits
unsigned int counter_additions_ips = 0; // " " " " … non inscrits
unsigned int revoked_contributor_additions = 0; // Nombre de traductions révoquées… de contributeurs inscrits
unsigned int revoked_ip_additions = 0; // " " " … de contributeurs non inscrits
string remember; // diffs à mettre de côté car résumé d'édition tronqué
string remember_tmp; // Variable temporaire (transférée si traductions correspondantes non révoquées)
int cnt_rem = 0; // Nombre de diffs à analyser
int cnt_rem_tmp = 0;
/**************/
// Transfert d’une map (temporaire) à une autre (dénombrant les résultats finaux)
void transfer_between_maps(map<string, int> &map_source, map<string, int> &map_dest) {
for(auto const &item : map_source) {
if (map_dest) {
map_dest += item.second;
} else {
map_dest = item.second;
}
}
}
// Vide les variables temporaires après un transfert
void empty_all_tmp_vars() {
stats_langs_tmp = {};
stats_dates_tmp = {};
stats_contribs_tmp = {};
stats_ips_tmp = {};
total_count_contribs_tmp = 0;
total_count_ips_tmp = 0;
cnt_rem_tmp = 0;
remember_tmp = "";
}
// Transfert de toutes les variables temporaires vers leurs versions persistantes
void transfer_all_tmp_vars() {
transfer_between_maps(stats_langs_tmp, stats_langs);
transfer_between_maps(stats_dates_tmp, stats_dates);
transfer_between_maps(stats_contribs_tmp, stats_contribs);
transfer_between_maps(stats_ips_tmp, stats_ips);
total_count_contribs += total_count_contribs_tmp;
total_count_ips += total_count_ips_tmp;
counter_trans += (total_count_contribs_tmp + total_count_ips_tmp);
remember += remember_tmp;
cnt_rem += cnt_rem_tmp;
empty_all_tmp_vars();
}
// Fonction effectuant le traitement de la Partie 1
void count_trads_added_with_te()
{
/**** FICHIERS D'ENTREE ET DE SORTIE ****/
ifstream infile(input_file_history, ifstream::in);
if (!infile) {
cout << "Le dump n'est pas situe a " << input_file_history << endl;
return;
}
ofstream out_res(output_file_res, ofstream::out);
if (!out_res) {
cout << "Probleme avec le fichier de sortie global " << endl;
return;
}
ofstream out_langs(output_file_langs, ofstream::out);
if (!out_langs) {
cout << "Probleme avec le fichier de sortie sur les langues" << endl;
return;
}
ofstream out_dates(output_file_dates, ofstream::out);
if (!out_dates) {
cout << "Probleme avec le fichier de sortie sur les dates" << endl;
return;
}
ofstream out_contribs(output_file_contribs, ofstream::out);
if (!out_contribs) {
cout << "Probleme avec le fichier de sortie sur les contributeurs" << endl;
return;
}
ofstream out_ips(output_file_ips, ofstream::out);
if (!out_ips) {
cout << "Probleme avec le fichier de sortie sur les ips" << endl;
return;
}
ofstream out_diff(output_file_diffs, ofstream::out);
if (!out_diff) {
cout << "Probleme avec le fichier de sortie de diffs" << endl;
return;
}
/***************************************/
/*** PARAMETRES ***/
// Pour s’assurer que la révision en cours est bien dans la période d’analyse désirée (et définie plus haut)
int from_year = stoi(from_date.substr(0, 4));
int from_month = stoi(from_date.substr(5, 2));
int to_year = stoi(to_date.substr(0, 4));
int to_month = stoi(to_date.substr(5, 2));
/******************/
/**** REVISION EN COURS ****/
bool pending_rev = false;
bool additions_to_count = false; // Ajouts à décompter ? Variable vidée après s'être assurée que les ajouts décomptés n'ont pas été annulés
unsigned int ip_additions_to_count = 0; // Nombre d'ajouts d'IPs à décompter comme révoqués si tel est le cas
unsigned int contrib_additions_to_count = 0; // " " de contributeurs " " " "
bool id_read = false;
string title, rev_id, prev_rev_id; // Informations utiles pour les ajouts dont on veut analyser les diffs
int ns = -1;
string tstamp; // Timestamp de la révision en cours d'analyse
int year, month;
string contributor; // Nom du contributeur ou adresse IP
bool is_ip = false; // Si le contributeur est enregistré ou non
string comment = ""; // résumé d’édition en cours d’analyse
string lang; // langue de la traduction en cours d’analyse (langues des traductions mentionnées dans le résumé d'édition)
string line; // Ligne du dump actuellement en cours d’analyse
size_t pos1, pos2, pos3;
bool entry_with_trans = false;
/***************************/
while (getline(infile, line)) {
if (line.find("<title>") != string::npos) {
pos1 = line.find("<title>");
pos2 = line.find("</title>");
title= line.substr(pos1+7, pos2-pos1-7);
counter_pages++;
}
// Si pas main, on passe
if (line.find("<ns>") != string::npos) {
pos1 = line.find("<ns>");
pos2 = line.find("</ns>");
ns = stoi(line.substr(pos1+4, pos2-pos1-4));
}
if (ns != 0) {
continue;
}
if (line.find("<revision>") != string::npos) {
pending_rev = true;
id_read = false;
}
if (pending_rev) {
if (!id_read && line.find("<id>") != string::npos) {
pos1 = line.find("<id>");
pos2 = line.find("</id>");
prev_rev_id = rev_id;
rev_id = line.substr(pos1+4, pos2-pos1-4);
id_read = true;
}
// On verifie que l'édition est faite dans la période choisie
if (line.find("<timestamp>") != string::npos) {
pos1 = line.find("<timestamp>");
pos2 = line.find("</timestamp>");
tstamp = line.substr(pos1+11, 7);
year = stoi( tstamp.substr(0, 4) );
month = stoi( tstamp.substr(5, 2) );
if (year < from_year || (year == from_year && month < from_month) ||
year > to_year || (year == to_year && month >= to_month)
) {
pending_rev = false;
if (additions_to_count) {
transfer_all_tmp_vars();
additions_to_count = false;
}
continue;
}
}
// Pour un utilisateur on aura dans le fichier XML <username></username>, tandis que pour une IP on aura <ip></ip>
if (line.find("<username>") != string::npos) {
pos1 = line.find("<username>");
pos2 = line.find("</username>");
contributor = line.substr(pos1+10, pos2-pos1-10);
is_ip = false;
}
if (line.find("<ip>") != string::npos) {
pos1 = line.find("<ip>");
pos2 = line.find("</ip>");
contributor = line.substr(pos1+4, pos2-pos1-4);
is_ip = true;
}
// On analyse tous les commentaires des pages de l’espace principal à la recherche de
// résumés d’édition correspondant à des ajouts traductions avec Translation editor
if (line.find("<comment>") != string::npos) {
pos1 = line.find("<comment>");
pos2 = line.find("</comment>");
comment = line.substr(pos1+9, pos2-pos1-9);
if (comment.substr(0, 15) == "Traductions : +") { // cas 1 : traductions ajoutées avec l'outil : on met de côté
// avant de s’assurer qu'elles n'ont pas été révoquées
additions_to_count = true;
counter_additions++;
if (is_ip) {
ip_additions_to_count++;
counter_additions_ips++;
} else {
contrib_additions_to_count++;
counter_additions_contributors++;
}
// Si le résumé d’édition est tronqué, on garde les infos de côté pour analyser + tard les diffs
if (comment.find("(assist") == string::npos) {
remember_tmp += "title=" + title + ";prev_rev_id=" + prev_rev_id + ";rev_id=" + rev_id +
";contrib=" + contributor + ";is_ip=" + (is_ip ? "true" : "false") +
";date=" + tstamp + "\n";
cnt_rem_tmp++;
continue;
}
entry_with_trans = true;
pos3 = 11;
do {
pos3 += 4;
lang = comment.substr(pos3, comment.find(" :", pos3) - pos3);
if (lang == "en-tête") continue; // On ignore les modifications d’en-tête
if (stats_langs_tmp.count(lang)) {
stats_langs_tmp++;
} else {
stats_langs_tmp = 1;
}
if (stats_dates_tmp.count(tstamp)) {
stats_dates_tmp++;
} else {
stats_dates_tmp = 1;
}
if (!is_ip) {
if (stats_contribs_tmp.count(contributor)) {
stats_contribs_tmp++;
} else {
stats_contribs_tmp = 1;
}
total_count_contribs_tmp++;
} else {
if (stats_ips_tmp.count(contributor)) {
stats_ips_tmp++;
} else {
stats_ips_tmp = 1;
}
total_count_ips_tmp++;
}
pos3 = comment.find(" ; +", pos3);
} while (pos3 != string::npos); // On répète la boucle pour toutes les traductions présentes dans le résumé d’édition
} else if ((comment.substr(0, 15) == "R\u00E9vocation des"
|| comment.substr(0, 14) == "Annulation des")
&& additions_to_count
) { // cas 2 : révocation d'ajout(s) de traduction : on ignore
revoked_contributor_additions += contrib_additions_to_count;
revoked_ip_additions += ip_additions_to_count;
empty_all_tmp_vars();
additions_to_count = false;
ip_additions_to_count = 0;
contrib_additions_to_count = 0;
entry_with_trans = false;
} else if (additions_to_count) { // cas 3 : non-révocation après ajout de traductions :
// on compte les trads précédemment analysées
transfer_all_tmp_vars();
additions_to_count = false;
ip_additions_to_count = 0;
contrib_additions_to_count = 0;
}
}
if (line.find("</revision>") != string::npos) {
pending_rev = false;
}
}
if (line.find("</title>") != string::npos) {
if (additions_to_count) { // cas ou la derniere modif d'une page est un ajout de traduction (non révoqué) :
// on prend alors en compte les ajouts de trads
transfer_all_tmp_vars();
additions_to_count = false;
ip_additions_to_count = 0;
contrib_additions_to_count = 0;
}
if (entry_with_trans) {
counter_pages_with_trans++;
// Tous les 100 pages modifiées avec Translation editor, on affiche des stats récapitulatives
if (counter_pages_with_trans % 100 == 0) {
cout << counter_pages_with_trans << " pages modifiees sur " <<
counter_pages << " scannees - " << counter_trans << " trads ajoutees avec TE (et "
<< revoked_contributor_additions + revoked_ip_additions << "/"
<< counter_additions << " ajouts revoques) contribs: "
<< counter_additions_contributors << " ; IPs: " << counter_additions_ips << endl;
}
// Tous les 10 000 pages modifiées avec Translation editor, on affiche les stats accumulés jusqu’alors
// (pour le suivi et le repérage anticipé d’aberrations dans les résultats)
if (counter_pages_with_trans % 10000 == 0) {
for (const auto &p : stats_langs) {
std::cout << p.first << " : " << p.second << '\n';
}
for (const auto &p : stats_dates) {
std::cout << p.first << " : " << p.second << '\n';
}
for (const auto &p : stats_contribs) {
std::cout << p.first << " : " << p.second << '\n';
}
std::cout << "Total contributeurs : " << total_count_contribs << '\n';
std::cout << "Total IPs : " << total_count_ips << '\n';
cout << cnt_rem << " diffs a analyser en +" << endl;
}
}
entry_with_trans = false;
}
}
cout << cnt_rem << " diffs a analyser en +" << endl;
cout << counter_pages_with_trans << " pages modifiees avec translation_editor (" << counter_trans << " traductions)" << endl;
infile.close();
out_res << "* Traductions ajoutées : " << counter_trans << endl;
out_res << "* Ajouts avec l'outil : " << counter_additions << " (contributeurs : " << counter_additions_contributors
<< " ; IPs : " << counter_additions_ips << ")" << endl;
out_res << "* Pages modifiées avec translation_editor : " << counter_pages_with_trans << endl;
out_res << "* Traductions ajoutées par des utilisateurs inscrits : " << total_count_contribs << endl;
out_res << "* Traductions ajoutées par des utilisateurs non inscrits : " << total_count_ips << endl;
out_res << "* Ajouts de contributeurs révoqués : " << revoked_contributor_additions << endl;
out_res << "* Ajouts d'IPs révoqués : " << revoked_ip_additions << endl;
// Ecriture des resultats dans le fichier de sortie
for (const auto &p : stats_langs) {
out_langs << p.first << " : " << p.second << endl;
}
for (const auto &p : stats_dates) {
out_dates << p.first << " : " << p.second << endl;
}
for (const auto &p : stats_contribs) {
out_contribs << p.first << " : " << p.second << endl;
}
for (const auto &p : stats_ips) {
out_ips << p.first << " : " << p.second << endl;
}
out_res.close();
out_langs.close();
out_dates.close();
out_contribs.close();
out_ips.close();
// Entrees restantes à traiter en analysant les diffs => dans un fichier a part
out_diff << remember;
out_diff.close();
}
int main()
{
count_trads_added_with_te();
cout << "End of processing!" << endl;
return 0;
}
// Traductions décomptées sur le dump du 1/12/2023 : 494138
Ce script a pris ~30 minutes à s’exécuter (Windows 10, processeur quadricore (2.3GHz), 8 Go de RAM).
#!/usr/bin/env python3
import os
import re
import time
import pywikibot
import ast
from subprocess import call
# Copie de ] (la liste étant préfixée de languages =)
from languages_list import languages
site = pywikibot.Site(code='fr', fam='wiktionary')
# Dossier qui contient les résultats générés précédemment et notamment le fichier
# stats-trads-diffs-to-check.txt qui contient les infos sur les diffs à analyser.
folder = os.path.dirname(__file__)
# Fichiers de sortie
file_langs = 'stats-langs-after-diffs.txt'
file_dates = 'stats-months-after-diffs.txt'
file_contributors = 'stats-trads-contributors-after-diffs.txt'
file_ips = 'stats-trads-ips-after-diffs.txt'
def first_n_items_dict(d, n, first_index):
'''Returns a list of n top-valued items from index first_index'''
return sorted(d.items(), key=lambda x: x, reverse=True)
def dict_from_file(file, folder=folder):
with open(os.path.join(folder, file), encoding='utf-8') as f:
dict_results = ast.literal_eval(f.read())
return dict_results
def generate_graph(results, file_graph='graph_template.txt', cat='langs', label='lang'):
'''
@param results: an object containing the results
@type results: str representing a file,
or dict or list of tuples
'''
input_file = os.path.join(folder, file_graph)
output_file = os.path.join(folder, 'graph-{}.txt'.format(cat))
if isinstance(results, str):
with open(os.path.join(folder, results)) as f:
results = ast.literal_eval(f.read())
with open(input_file, 'r', encoding='utf-8') as f:
content = f.read()
specific_results = ''
if isinstance(results, dict):
for k in results:
specific_results += ' {{"{}": "{}", "amount": {} }},\n'.format(label, k, str(results))
elif isinstance(results, list):
for item in results:
specific_results += ' {{"{}": "{}", "amount": {} }},\n'.format(label, item, str(item))
else:
print('results objects should be either a string, a list or a dict, but it is a {}'.format(type(results))); return
specific_results = specific_results # removing last ",\n"
content = content.replace('__TO_REPLACE__', specific_results)
content = content.replace('__LABEL__', label)
oblique_labels = '__OBLIQUE_LABELS__'
if cat == 'dates':
content = content.replace(oblique_labels, ', "properties": { "labels": {"angle": {"value": -45}, "dx": {"value": -20} } } ')
else:
content = content.replace(oblique_labels, '')
with open(output_file, 'w', encoding='utf-8') as f:
f.write(content)
print('Graph saved to {}'.format(output_file))
def generate_langs_graph(nbLangs=15, first_index=0, filename_var='langs'):
d = dict_from_file(file_langs)
data = first_n_items_dict(d, nbLangs, first_index)
generate_graph(data, file_graph='graph_template.txt', cat=filename_var, label='lang')
def generate_dates_graph():
generate_graph(file_dates, file_graph='graph_template.txt', cat='dates', label='date')
def results_to_wikitable(dict, header1='Langues', header2='Traductions ajoutées'):
wikitext = '{{| class="wikitable sortable mw-collapsible"\n! {} !! {}'.format(header1, header2)
for k in dict:
wikitext += '\n|-\n| {} || {}'.format(k, str(dict))
wikitext += '\n|}'
return wikitext
def raw_results_to_wikitable(filename, dest=None, header1='Langues', header2='Traductions ajoutées'):
if dest is None:
raise Exception('A destination file must be provided')
input_file = os.path.join(folder, filename)
output_file = os.path.join(folder, dest)
with open(input_file, encoding='utf-8') as f:
for line in f:
dict_results = ast.literal_eval(line)
break
with open(output_file, 'w+', encoding='utf-8') as f:
f.write(results_to_wikitable(dict_results, header1, header2))
print('Wikitable saved to {}'.format(output_file))
def count_trads_per_diff():
'''
Génère des statistiques d'ajout de trads lorsque les résumés d'édition sont tronqués
et necessitent une analyse des diffs
'''
stats_langs = {}
stats_dates = {}
stats_contribs = {}
stats_ips = {}
cpt = 0
cpt_trads = 0
count_contributors = 0
count_ips = 0
with open(os.path.join(folder, "stats-langs.txt"), encoding="utf-8") as f:
for line in f:
match = re.search("(.+) : (\d+)", line)
if match is None:
continue
stats_langs] = int(match)
with open(os.path.join(folder, "stats-months.txt"), encoding="utf-8") as f:
for line in f:
match = re.search("(.+) : (\d+)", line)
stats_dates] = int(match)
with open(os.path.join(folder, "stats-trads-contributors.txt"), encoding="utf-8") as f:
for line in f:
match = re.search("(.+) : (\d+)", line)
if match is None:
continue
stats_contribs] = int(match)
with open(os.path.join(folder, "stats-trads-ips.txt"), encoding="utf-8") as f:
for line in f:
match = re.search("(.+) : (\d+)", line)
if match is None:
continue
stats_ips] = int(match)
with open(os.path.join(folder, "stats-trads-diffs-to-check.txt"), encoding="utf-8") as f:
for line in f:
# line = "title=lire;prev_rev_id=18770633;rev_id=18908602;contrib=Test;is_ip=false;date=2015-01"
# title = line.split(';').split('=')
from_rev = line.split(';').split('=')
to_rev = line.split(';').split('=')
contrib = line.split(';').split('=')
if line.split(';').split('=') == 'true':
is_ip = True
else:
is_ip = False
date = line.split(';').split('=').strip()
diff_html = site.compare(old=int(from_rev), diff=int(to_rev))
# The Mediawiki diff algorithm sometimes shows existing translations as removed lines
# (cf. eg. https://fr.wiktionary.orghttps://fr.wiktionary.org/w/index.php?title=boto&diff=prev&oldid=25504237),
# and then shows them back in the added lines.
# Hence we collect all language codes in the added translations (A), and in the removed translations (B),
# and we do the diff, by substracting B from A (as multisets).
# This works as translation_editor is only used to add translations.
# Note: the previously used algorithm (]) was almost twice as efficient,
# and had an error margin of just about ~100 entries out of 77,500, that is a ~0.13 % error margin.
trads_removed = re.findall('<td class="diff-deletedline diff-side-deleted"><div>(.+)</div></td>', diff_html)
trads_added = re.findall('<td class="diff-addedline diff-side-added"><div>(.+)</div></td>', diff_html)
codes_added =
codes_removed =
for t in trads_added:
codes_added += re.findall('{{trad(?:<ins class=\"diffchange diffchange-inline\">)?{0,2}(?:</ins>)?\|(?:<ins class="diffchange diffchange-inline">)?(+)(?:</ins>)?\|', t)
for t in trads_removed:
codes_removed += re.findall('{{trad(?:<del class=\"diffchange diffchange-inline\">)?{0,2}(?:</del>)?\|(?:<del class="diffchange diffchange-inline">)?(+)(?:</del>)?\|', t)
# Now that we collected the codes for all removed translations, we remove one instance
# of each code in the added translations.
for code in codes_removed:
codes_added.remove(code)
for code in codes_added:
if code in languages:
lang_name = languages
elif code in languages:
lang_name = languages]
else:
# Les codes langue non répertoriés dans la liste des langues sont ignorés
# 1 cas le 3/12/2023 : le code zh-tc supprimé depuis
print('CODE ' + code + ' NOT FOUND (and translation ignored) - ' + line.strip())
continue
if lang_name:
if lang_name in stats_langs:
stats_langs += 1
else:
stats_langs = 1
if is_ip:
if contrib in stats_ips:
stats_ips += 1
else:
stats_ips = 1
count_ips += 1
else:
if contrib in stats_contribs:
stats_contribs += 1
else:
stats_contribs = 1
count_contributors += 1
if date in stats_dates:
stats_dates += 1
else:
stats_dates = 1
cpt_trads += 1
cpt += 1
if cpt % 100 == 0:
print(str(cpt) + " diffs traites (" + str(cpt_trads) + " traductions)")
with open(os.path.join(folder, "stats-trads-res-after-diffs.txt"), "w+", encoding="utf-8") as f:
res = "Résultats des stats sur les résumés d'édition tronqués (ajout de traductions par lots) :\n"
res += "Traductions ajoutées : " + str(cpt_trads) + "\n"
res += "Traductions ajoutées par des utilisateurs inscrits : " + str(count_contributors) + "\n"
res += "Traductions ajoutées par des utilisateurs non inscrits : " + str(count_ips) + "\n"
f.write(res)
with open(os.path.join(folder, "stats-langs-after-diffs.txt"), "w+", encoding="utf-8") as f:
f.write(str(stats_langs))
with open(os.path.join(folder, "stats-months-after-diffs.txt"), "w+", encoding="utf-8") as f:
f.write(str(stats_dates))
with open(os.path.join(folder, "stats-trads-contributors-after-diffs.txt"), "w+", encoding="utf-8") as f:
f.write(str(stats_contribs))
with open(os.path.join(folder, "stats-trads-ips-after-diffs.txt"), "w+", encoding="utf-8") as f:
f.write(str(stats_ips))
if __name__ == '__main__':
start_time = time.time()
count_trads_per_diff()
print("--- %s seconds ---" % (time.time() - start_time))
# Création des fichiers de stats
raw_results_to_wikitable(file_dates, dest='wikitable-dates.txt', header1='Mois', header2='Traductions ajoutées')
raw_results_to_wikitable(file_langs, dest='wikitable-langs.txt', header1='Langue', header2='Traductions ajoutées')
raw_results_to_wikitable(file_ips, dest='wikitable-ips.txt', header1='Utilisateur non enregistré', header2='Traductions ajoutées')
raw_results_to_wikitable(file_contributors, dest='wikitable-contributors.txt', header1='Utilisateur enregistré', header2='Traductions ajoutées')
# Disabling graphs generation, as the Graph extension is disabled on Wikimedia wikis
# as of 1/2024 due to security issues.
# generate_langs_graph()
# generate_langs_graph(15, 15, filename_var='langs2')
# generate_dates_graph()
Ce script a pris ~20 minutes à s’exécuter (Windows 10, processeur quadricore (2.3GHz), 8 Go de RAM)
Il fait référence au fichier graph_template.txt suivant pour la génération des graphiques :
{{#tag:graph| { "version": 4, "width": 1000, "height": 200, "padding": {"top": 20, "left": 65, "bottom": 60, "right": 10}, "data": [ { "name": "table", "values": [ __TO_REPLACE__ ] } ], "signals": [ { "name": "tooltip", "init": {}, "streams": [ {"type": "rect:mouseover", "expr": "datum"}, {"type": "rect:mouseout", "expr": "{}"} ] } ], "predicates": [ { "name": "tooltip", "type": "==", "operands": } ], "scales": [ { "name": "xscale", "type": "ordinal", "range": "width", "domain": {"data": "table", "field": "__LABEL__"} }, { "name": "yscale", "type": "linear", "range": "height", "domain": {"data": "table", "field": "amount"} } ], "axes": [ { "type": "x", "scale": "xscale"__OBLIQUE_LABELS__}, { "type": "y", "scale": "yscale" } ], "marks": [ { "type": "rect", "from": {"data":"table"}, "properties": { "enter": { "x": {"scale": "xscale", "field": "__LABEL__"}, "width": {"scale": "xscale", "band": true, "offset": -1}, "y": {"scale": "yscale", "field": "amount"}, "y2": {"field": {"group": "height"} } }, "update": { "fill": {"value": "steelblue"} }, "hover": { "fill": {"value": "red"} } } }, { "type": "text", "properties": { "enter": { "align": {"value": "center"}, "fill": {"value": "#333"} }, "update": { "x": {"scale": "xscale", "signal": "tooltip.__LABEL__"}, "dx": {"scale": "xscale", "band": true, "mult": 0.5}, "y": {"scale": "yscale", "signal": "tooltip.amount", "offset": -5}, "text": {"signal": "tooltip.amount"}, "fillOpacity": { "rule": [ { "predicate": {"name": "tooltip", "id": {"value": null} }, "value": 0 }, {"value": 1} ] } } } } ] } | mode=interactive }}