up wiki controller
This commit is contained in:
parent
2f49ef6479
commit
391a212034
13 changed files with 1271297 additions and 866 deletions
|
@ -18,13 +18,14 @@ body {
|
|||
#qrcode {
|
||||
margin-bottom: 8rem;
|
||||
}
|
||||
input[data-important]{
|
||||
border-color: #7a8fbb ;
|
||||
border-left-width: 5px ;
|
||||
|
||||
input[data-important] {
|
||||
border-color: #7a8fbb;
|
||||
border-left-width: 5px;
|
||||
}
|
||||
|
||||
input[data-important]:before{
|
||||
content : ">" !important ;
|
||||
input[data-important]:before {
|
||||
content: ">" !important;
|
||||
}
|
||||
|
||||
.filled, .good_filled {
|
||||
|
@ -32,7 +33,7 @@ input[data-important]:before{
|
|||
color: #082b0a !important;
|
||||
}
|
||||
|
||||
.filled:hover , .good_filled:hover {
|
||||
.filled:hover, .good_filled:hover {
|
||||
background-color: #d9ffd1 !important;
|
||||
}
|
||||
|
||||
|
@ -174,13 +175,18 @@ table tbody {
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
#table_container, .table-container, #table-container{
|
||||
#table_container, .table-container, #table-container {
|
||||
max-height: 700px;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
border: solid 3px rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
#citySuggestions{
|
||||
#citySuggestions {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
body .card:hover {
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
104
public/osm_fr_groups.json
Normal file
104
public/osm_fr_groups.json
Normal file
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"last_updated": "2025-08-22T17:10:41.058478",
|
||||
"local_groups": [
|
||||
{
|
||||
"name": "Liste des groupes locaux se réunissant régulièrement",
|
||||
"url": "https://framacalc.org/osm-groupes-locaux",
|
||||
"description": "",
|
||||
"type": "local_group"
|
||||
},
|
||||
{
|
||||
"name": "Carte des groupes locaux se réunissant régulièrement",
|
||||
"url": "https://umap.openstreetmap.fr/fr/map/groupes-locaux-openstreetmap_152488",
|
||||
"description": "",
|
||||
"type": "local_group"
|
||||
}
|
||||
],
|
||||
"working_groups": [
|
||||
{
|
||||
"name": "Que venir faire au sein de l'association ?",
|
||||
"url": "https://forum.openstreetmap.fr/t/que-venir-faire-au-sein-de-lassociation/15454",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Inclusivité",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Inclusivité",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Technique",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Technique",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Communication externe",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Communication",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Animation de la communauté",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Animation_de_la_communauté",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Communautés locales",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Communautés_locales",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT International",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_International",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Gestion et comptabilité",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Gestion_et_comptabilité",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Soutiens",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Soutiens",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Conférence SotM-FR",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Conférence_SotM-FR",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "Groupes spéciaux",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#Groupes_spéciaux",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "Groupes projets et thématiques",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#Groupes_projets_et_thématiques",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
}
|
||||
],
|
||||
"umap_url": "https://umap.openstreetmap.fr/fr/map/groupes-locaux-openstreetmap_152488"
|
||||
}
|
1269523
public/pages_unavailable_in_french.json
Normal file
1269523
public/pages_unavailable_in_french.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -5,75 +5,302 @@ namespace App\Controller;
|
|||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class WikiController extends AbstractController
|
||||
{
|
||||
#[Route('/admin/wiki/missing-translations', name: 'app_admin_wiki_missing_translations')]
|
||||
#[Route('/wiki/missing-translations', name: 'app_admin_wiki_missing_translations')]
|
||||
public function missingTranslations(): Response
|
||||
{
|
||||
$csvFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/wiki_pages.csv';
|
||||
$untranslatedFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/untranslated_french_pages.json';
|
||||
|
||||
if (!file_exists($csvFile)) {
|
||||
$this->addFlash('error', 'Le fichier wiki_pages.csv n\'existe pas.');
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
// Initialize arrays
|
||||
$untranslatedPages = [];
|
||||
$lastUpdated = null;
|
||||
|
||||
// Check if the untranslated pages file exists and load it
|
||||
if (file_exists($untranslatedFile)) {
|
||||
$untranslatedData = json_decode(file_get_contents($untranslatedFile), true);
|
||||
|
||||
if (isset($untranslatedData['untranslated_pages']) && is_array($untranslatedData['untranslated_pages'])) {
|
||||
$untranslatedPages = $untranslatedData['untranslated_pages'];
|
||||
$lastUpdated = isset($untranslatedData['last_updated']) ? $untranslatedData['last_updated'] : null;
|
||||
}
|
||||
|
||||
$csvData = array_map('str_getcsv', file($csvFile));
|
||||
$headers = array_shift($csvData);
|
||||
// Check if the data is older than 1 hour
|
||||
if ($lastUpdated) {
|
||||
$lastUpdatedTime = new \DateTime($lastUpdated);
|
||||
$now = new \DateTime();
|
||||
$diff = $now->diff($lastUpdatedTime);
|
||||
|
||||
$wikiPages = [];
|
||||
|
||||
// Process CSV data
|
||||
foreach ($csvData as $row) {
|
||||
$page = array_combine($headers, $row);
|
||||
$wikiPages[$page['key']][$page['language']] = $page;
|
||||
}
|
||||
|
||||
// Find French pages without English translations
|
||||
$frenchOnlyPages = [];
|
||||
foreach ($wikiPages as $key => $languages) {
|
||||
if (isset($languages['fr']) && !isset($languages['en'])) {
|
||||
$frenchOnlyPages[$key] = $languages['fr'];
|
||||
// If older than 1 hour, refresh the data
|
||||
if ($diff->h >= 1 || $diff->days > 0) {
|
||||
$this->refreshUntranslatedPagesData();
|
||||
return $this->redirectToRoute('app_admin_wiki_missing_translations');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the file doesn't exist, try to create it by running the script
|
||||
$this->refreshUntranslatedPagesData();
|
||||
|
||||
// Sort by key
|
||||
ksort($frenchOnlyPages);
|
||||
// Check if the file was created
|
||||
if (file_exists($untranslatedFile)) {
|
||||
return $this->redirectToRoute('app_admin_wiki_missing_translations');
|
||||
} else {
|
||||
$this->addFlash('error', 'Impossible de générer le fichier des pages sans traduction.');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('admin/wiki_missing_translations.html.twig', [
|
||||
'french_only_pages' => $frenchOnlyPages
|
||||
'untranslated_pages' => $untranslatedPages,
|
||||
'last_updated' => $lastUpdated
|
||||
]);
|
||||
}
|
||||
#[Route('/admin/wiki/suspicious-deletions', name: 'app_admin_wiki_suspicious_deletions')]
|
||||
|
||||
/**
|
||||
* Refresh the untranslated pages data by running the find_untranslated_french_pages.py script
|
||||
*/
|
||||
private function refreshUntranslatedPagesData(): void
|
||||
{
|
||||
try {
|
||||
$scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/find_untranslated_french_pages.py';
|
||||
if (file_exists($scriptPath)) {
|
||||
exec('python3 ' . $scriptPath . ' --force 2>&1', $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
$this->addFlash('warning', 'Impossible de mettre à jour les pages sans traduction. Erreur: ' . implode("\n", $output));
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('error', 'Le script find_untranslated_french_pages.py n\'existe pas.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlash('error', 'Erreur lors de l\'exécution du script: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/wiki/pages-unavailable-in-french', name: 'app_admin_wiki_pages_unavailable_in_french')]
|
||||
public function pagesUnavailableInFrench(): Response
|
||||
{
|
||||
$unavailablePagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/pages_unavailable_in_french.json';
|
||||
|
||||
// Initialize arrays
|
||||
$groupedPages = [];
|
||||
$allPages = [];
|
||||
$lastUpdated = null;
|
||||
|
||||
// Check if the unavailable pages file exists and load it
|
||||
if (file_exists($unavailablePagesFile)) {
|
||||
$unavailableData = json_decode(file_get_contents($unavailablePagesFile), true);
|
||||
|
||||
if (isset($unavailableData['grouped_pages']) && is_array($unavailableData['grouped_pages'])) {
|
||||
$groupedPages = $unavailableData['grouped_pages'];
|
||||
}
|
||||
|
||||
if (isset($unavailableData['all_pages']) && is_array($unavailableData['all_pages'])) {
|
||||
$allPages = $unavailableData['all_pages'];
|
||||
}
|
||||
|
||||
$lastUpdated = isset($unavailableData['last_updated']) ? $unavailableData['last_updated'] : null;
|
||||
|
||||
// Check if the data is older than 1 hour
|
||||
if ($lastUpdated) {
|
||||
$lastUpdatedTime = new \DateTime($lastUpdated);
|
||||
$now = new \DateTime();
|
||||
$diff = $now->diff($lastUpdatedTime);
|
||||
|
||||
// If older than 1 hour, refresh the data
|
||||
if ($diff->h >= 1 || $diff->days > 0) {
|
||||
$this->refreshPagesUnavailableInFrenchData();
|
||||
return $this->redirectToRoute('app_admin_wiki_pages_unavailable_in_french');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the file doesn't exist, try to create it by running the script
|
||||
$this->refreshPagesUnavailableInFrenchData();
|
||||
|
||||
// Check if the file was created
|
||||
if (file_exists($unavailablePagesFile)) {
|
||||
return $this->redirectToRoute('app_admin_wiki_pages_unavailable_in_french');
|
||||
} else {
|
||||
$this->addFlash('error', 'Impossible de générer le fichier des pages non disponibles en français.');
|
||||
}
|
||||
}
|
||||
|
||||
// Move English pages to the top of the list
|
||||
$englishPages = $groupedPages['En'] ?? [];
|
||||
unset($groupedPages['En']);
|
||||
|
||||
// Sort other language groups alphabetically
|
||||
ksort($groupedPages);
|
||||
|
||||
// Reinsert English pages at the beginning
|
||||
if (!empty($englishPages)) {
|
||||
$groupedPages = ['En' => $englishPages] + $groupedPages;
|
||||
}
|
||||
|
||||
return $this->render('admin/wiki_pages_unavailable_in_french.html.twig', [
|
||||
'grouped_pages' => $groupedPages,
|
||||
'all_pages' => $allPages,
|
||||
'last_updated' => $lastUpdated
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the pages unavailable in French data by running the find_pages_unavailable_in_french.py script
|
||||
*/
|
||||
private function refreshPagesUnavailableInFrenchData(): void
|
||||
{
|
||||
try {
|
||||
$scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/find_pages_unavailable_in_french.py';
|
||||
if (file_exists($scriptPath)) {
|
||||
exec('python3 ' . $scriptPath . ' --force 2>&1', $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
$this->addFlash('warning', 'Impossible de mettre à jour les pages non disponibles en français. Erreur: ' . implode("\n", $output));
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('error', 'Le script find_pages_unavailable_in_french.py n\'existe pas.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlash('error', 'Erreur lors de l\'exécution du script: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/wiki/osm-fr-groups', name: 'app_admin_wiki_osm_fr_groups')]
|
||||
public function osmFrGroups(): Response
|
||||
{
|
||||
$groupsFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/osm_fr_groups.json';
|
||||
|
||||
// Initialize arrays
|
||||
$workingGroups = [];
|
||||
$localGroups = [];
|
||||
$umapUrl = 'https://umap.openstreetmap.fr/fr/map/groupes-locaux-openstreetmap_152488';
|
||||
$lastUpdated = null;
|
||||
|
||||
// Check if the groups file exists and load it
|
||||
if (file_exists($groupsFile)) {
|
||||
$groupsData = json_decode(file_get_contents($groupsFile), true);
|
||||
|
||||
if (isset($groupsData['working_groups']) && is_array($groupsData['working_groups'])) {
|
||||
$workingGroups = $groupsData['working_groups'];
|
||||
}
|
||||
|
||||
if (isset($groupsData['local_groups']) && is_array($groupsData['local_groups'])) {
|
||||
$localGroups = $groupsData['local_groups'];
|
||||
}
|
||||
|
||||
$umapUrl = isset($groupsData['umap_url']) ? $groupsData['umap_url'] : 'https://umap.openstreetmap.fr/fr/map/groupes-locaux-openstreetmap_152488';
|
||||
$lastUpdated = isset($groupsData['last_updated']) ? $groupsData['last_updated'] : null;
|
||||
|
||||
// Check if the data is older than 1 hour
|
||||
if ($lastUpdated) {
|
||||
$lastUpdatedTime = new \DateTime($lastUpdated);
|
||||
$now = new \DateTime();
|
||||
$diff = $now->diff($lastUpdatedTime);
|
||||
|
||||
// If older than 1 hour, refresh the data
|
||||
// if ($diff->h >= 1 || $diff->days > 0) {
|
||||
// $this->refreshOsmFrGroupsData();
|
||||
// return $this->redirectToRoute('app_admin_wiki_osm_fr_groups');
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
// If the file doesn't exist, try to create it by running the script
|
||||
$this->refreshOsmFrGroupsData();
|
||||
|
||||
// Check if the file was created
|
||||
if (file_exists($groupsFile)) {
|
||||
// return $this->redirectToRoute('app_admin_wiki_osm_fr_groups');
|
||||
} else {
|
||||
$this->addFlash('error', 'Impossible de générer le fichier des groupes OSM-FR.');
|
||||
}
|
||||
}
|
||||
|
||||
// Group working groups by category
|
||||
$groupedWorkingGroups = [];
|
||||
foreach ($workingGroups as $group) {
|
||||
$category = $group['category'] ?? 'Autres';
|
||||
if (!isset($groupedWorkingGroups[$category])) {
|
||||
$groupedWorkingGroups[$category] = [];
|
||||
}
|
||||
$groupedWorkingGroups[$category][] = $group;
|
||||
}
|
||||
|
||||
// Sort categories alphabetically
|
||||
ksort($groupedWorkingGroups);
|
||||
|
||||
return $this->render('admin/wiki_osm_fr_groups.html.twig', [
|
||||
'working_groups' => $groupedWorkingGroups,
|
||||
'local_groups' => $localGroups,
|
||||
'umap_url' => $umapUrl,
|
||||
'last_updated' => $lastUpdated
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the OSM-FR groups data by running the fetch_osm_fr_groups.py script
|
||||
*/
|
||||
private function refreshOsmFrGroupsData(): void
|
||||
{
|
||||
try {
|
||||
$scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/fetch_osm_fr_groups.py';
|
||||
if (file_exists($scriptPath)) {
|
||||
exec('python3 ' . $scriptPath . ' --force 2>&1', $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
$this->addFlash('warning', 'Impossible de mettre à jour les groupes OSM-FR. Erreur: ' . implode("\n", $output));
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('error', 'Le script fetch_osm_fr_groups.py n\'existe pas.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlash('error', 'Erreur lors de l\'exécution du script: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/wiki/suspicious-deletions', name: 'app_admin_wiki_suspicious_deletions')]
|
||||
public function suspiciousDeletions(): Response
|
||||
{
|
||||
$csvFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/wiki_pages.csv';
|
||||
$jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json';
|
||||
$suspiciousDeletesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/suspicious_deletions.json';
|
||||
$wordDiffFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json';
|
||||
|
||||
if (!file_exists($csvFile) || !file_exists($jsonFile)) {
|
||||
$this->addFlash('error', 'Les fichiers nécessaires n\'existent pas.');
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
}
|
||||
|
||||
// Load CSV data
|
||||
$csvData = array_map('str_getcsv', file($csvFile));
|
||||
$headers = array_shift($csvData);
|
||||
|
||||
// Process CSV data to find French pages
|
||||
$frenchPages = [];
|
||||
foreach ($csvData as $row) {
|
||||
$page = array_combine($headers, $row);
|
||||
if ($page['language'] === 'fr') {
|
||||
$frenchPages[$page['key']] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
// Load JSON data
|
||||
$jsonData = json_decode(file_get_contents($jsonFile), true);
|
||||
|
||||
// Find pages with suspicious deletions
|
||||
// Initialize arrays
|
||||
$suspiciousPages = [];
|
||||
$wordDiffPages = [];
|
||||
|
||||
// Check if the suspicious deletions file exists and load it
|
||||
if (file_exists($suspiciousDeletesFile)) {
|
||||
$suspiciousData = json_decode(file_get_contents($suspiciousDeletesFile), true);
|
||||
|
||||
if (isset($suspiciousData['deletions']) && is_array($suspiciousData['deletions'])) {
|
||||
$suspiciousPages = $suspiciousData['deletions'];
|
||||
$lastUpdated = isset($suspiciousData['last_updated']) ? $suspiciousData['last_updated'] : null;
|
||||
}
|
||||
} else {
|
||||
// If the file doesn't exist, try to create it by running the script
|
||||
try {
|
||||
$scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/detect_suspicious_deletions.py';
|
||||
if (file_exists($scriptPath)) {
|
||||
exec('python3 ' . $scriptPath . ' 2>&1', $output, $returnCode);
|
||||
|
||||
if ($returnCode === 0 && file_exists($suspiciousDeletesFile)) {
|
||||
$suspiciousData = json_decode(file_get_contents($suspiciousDeletesFile), true);
|
||||
if (isset($suspiciousData['deletions']) && is_array($suspiciousData['deletions'])) {
|
||||
$suspiciousPages = $suspiciousData['deletions'];
|
||||
$lastUpdated = isset($suspiciousData['last_updated']) ? $suspiciousData['last_updated'] : null;
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('warning', 'Impossible de générer le fichier de suppressions suspectes. Erreur: ' . implode("\n", $output));
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlash('error', 'Erreur lors de l\'exécution du script: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Also load the word-diff based suspicious pages for comparison
|
||||
if (file_exists($wordDiffFile)) {
|
||||
$jsonData = json_decode(file_get_contents($wordDiffFile), true);
|
||||
|
||||
foreach ($jsonData as $page) {
|
||||
if (isset($page['fr_page']) && isset($page['en_page'])) {
|
||||
// Calculate deletion percentage
|
||||
|
@ -84,77 +311,125 @@ class WikiController extends AbstractController
|
|||
// If English has more words and the difference is significant (>30%)
|
||||
if ($wordDiff > 0 && $frWordCount > 0 && ($wordDiff / $enWordCount) > 0.3) {
|
||||
$page['deletion_percentage'] = round(($wordDiff / $enWordCount) * 100, 2);
|
||||
$suspiciousPages[] = $page;
|
||||
$wordDiffPages[] = $page;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by deletion percentage (highest first)
|
||||
usort($suspiciousPages, function($a, $b) {
|
||||
usort($wordDiffPages, function ($a, $b) {
|
||||
return $b['deletion_percentage'] <=> $a['deletion_percentage'];
|
||||
});
|
||||
}
|
||||
|
||||
return $this->render('admin/wiki_suspicious_deletions.html.twig', [
|
||||
'suspicious_pages' => $suspiciousPages
|
||||
'suspicious_pages' => $wordDiffPages,
|
||||
'recent_deletions' => $suspiciousPages,
|
||||
'last_updated' => $lastUpdated ?? null
|
||||
]);
|
||||
}
|
||||
#[Route('/admin/wiki/tag-proposals', name: 'app_admin_wiki_tag_proposals')]
|
||||
|
||||
#[Route('/wiki/tag-proposals', name: 'app_admin_wiki_tag_proposals')]
|
||||
public function tagProposals(): Response
|
||||
{
|
||||
// URL of the OSM wiki page that lists tag proposals
|
||||
$url = 'https://wiki.openstreetmap.org/wiki/Proposed_features';
|
||||
$proposalsFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/proposals.json';
|
||||
|
||||
try {
|
||||
$html = file_get_contents($url);
|
||||
// Initialize arrays
|
||||
$votingProposals = [];
|
||||
$recentProposals = [];
|
||||
$lastUpdated = null;
|
||||
|
||||
if ($html === false) {
|
||||
throw new \Exception('Failed to fetch the tag proposals page');
|
||||
// Check if the proposals file exists and load it
|
||||
if (file_exists($proposalsFile)) {
|
||||
$proposalsData = json_decode(file_get_contents($proposalsFile), true);
|
||||
|
||||
if (isset($proposalsData['voting_proposals']) && is_array($proposalsData['voting_proposals'])) {
|
||||
$votingProposals = $proposalsData['voting_proposals'];
|
||||
}
|
||||
|
||||
// Create a DOM parser
|
||||
$dom = new \DOMDocument();
|
||||
@$dom->loadHTML($html);
|
||||
$xpath = new \DOMXPath($dom);
|
||||
if (isset($proposalsData['recent_proposals']) && is_array($proposalsData['recent_proposals'])) {
|
||||
$recentProposals = $proposalsData['recent_proposals'];
|
||||
}
|
||||
|
||||
// Find the table with proposals
|
||||
$tables = $xpath->query("//table[contains(@class, 'wikitable')]");
|
||||
$proposals = [];
|
||||
$lastUpdated = isset($proposalsData['last_updated']) ? $proposalsData['last_updated'] : null;
|
||||
|
||||
if ($tables->length > 0) {
|
||||
// Get the first table which contains the active proposals
|
||||
$table = $tables->item(0);
|
||||
$rows = $xpath->query(".//tr", $table);
|
||||
// Check if the data is older than 1 hour
|
||||
if ($lastUpdated) {
|
||||
$lastUpdatedTime = new \DateTime($lastUpdated);
|
||||
$now = new \DateTime();
|
||||
$diff = $now->diff($lastUpdatedTime);
|
||||
|
||||
// Skip the header row
|
||||
for ($i = 1; $i < $rows->length; $i++) {
|
||||
$row = $rows->item($i);
|
||||
$cells = $xpath->query(".//td", $row);
|
||||
// If older than 1 hour, refresh the data
|
||||
if ($diff->h >= 1 || $diff->days > 0) {
|
||||
$this->refreshProposalsData();
|
||||
return $this->redirectToRoute('app_admin_wiki_tag_proposals');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the file doesn't exist, try to create it by running the script
|
||||
$this->refreshProposalsData();
|
||||
|
||||
if ($cells->length >= 4) {
|
||||
$proposal = [
|
||||
'feature' => $xpath->query(".//a", $cells->item(0))->item(0)->textContent,
|
||||
'url' => 'https://wiki.openstreetmap.org' . $xpath->query(".//a", $cells->item(0))->item(0)->getAttribute('href'),
|
||||
'description' => $cells->item(1)->textContent,
|
||||
'proposer' => $cells->item(2)->textContent,
|
||||
'status' => $cells->item(3)->textContent,
|
||||
// Check if the file was created
|
||||
if (file_exists($proposalsFile)) {
|
||||
return $this->redirectToRoute('app_admin_wiki_tag_proposals');
|
||||
} else {
|
||||
$this->addFlash('error', 'Impossible de générer le fichier de propositions.');
|
||||
}
|
||||
}
|
||||
|
||||
// Format the proposals for the template
|
||||
$formattedProposals = [];
|
||||
|
||||
foreach ($votingProposals as $proposal) {
|
||||
$formattedProposals[] = [
|
||||
'feature' => $proposal['title'],
|
||||
'url' => $proposal['url'],
|
||||
'description' => 'Proposition en cours de vote',
|
||||
'proposer' => '',
|
||||
'status' => 'Voting',
|
||||
'type' => 'voting'
|
||||
];
|
||||
}
|
||||
|
||||
$proposals[] = $proposal;
|
||||
}
|
||||
}
|
||||
foreach ($recentProposals as $proposal) {
|
||||
$formattedProposals[] = [
|
||||
'feature' => $proposal['title'],
|
||||
'url' => $proposal['url'],
|
||||
'description' => 'Dernière modification: ' . $proposal['last_modified'],
|
||||
'proposer' => $proposal['modified_by'],
|
||||
'status' => 'Draft',
|
||||
'type' => 'recent'
|
||||
];
|
||||
}
|
||||
|
||||
return $this->render('admin/wiki_tag_proposals.html.twig', [
|
||||
'proposals' => $proposals
|
||||
'proposals' => $formattedProposals,
|
||||
'last_updated' => $lastUpdated
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the proposals data by running the fetch_proposals.py script
|
||||
*/
|
||||
private function refreshProposalsData(): void
|
||||
{
|
||||
try {
|
||||
$scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/fetch_proposals.py';
|
||||
if (file_exists($scriptPath)) {
|
||||
exec('python3 ' . $scriptPath . ' --force 2>&1', $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
$this->addFlash('warning', 'Impossible de mettre à jour les propositions. Erreur: ' . implode("\n", $output));
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('error', 'Le script fetch_proposals.py n\'existe pas.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlash('error', 'Erreur lors de la récupération des propositions de tags : ' . $e->getMessage());
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
$this->addFlash('error', 'Erreur lors de l\'exécution du script: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/admin/wiki/random-suggestion', name: 'app_admin_wiki_random_suggestion')]
|
||||
#[Route('/wiki/random-suggestion', name: 'app_admin_wiki_random_suggestion')]
|
||||
public function randomSuggestion(): Response
|
||||
{
|
||||
$jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json';
|
||||
|
@ -179,7 +454,7 @@ class WikiController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/wiki', name: 'app_admin_wiki')]
|
||||
#[Route('/wiki', name: 'app_admin_wiki')]
|
||||
public function index(): Response
|
||||
{
|
||||
$csvFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/wiki_pages.csv';
|
||||
|
@ -266,7 +541,7 @@ class WikiController extends AbstractController
|
|||
}
|
||||
|
||||
// Sort wiki pages by staleness score (descending)
|
||||
uasort($wikiPages, function($a, $b) {
|
||||
uasort($wikiPages, function ($a, $b) {
|
||||
$scoreA = isset($a['en']) && isset($a['fr']) && isset($a['en']['staleness_score']) ? (float)$a['en']['staleness_score'] : 0;
|
||||
$scoreB = isset($b['en']) && isset($b['fr']) && isset($b['en']['staleness_score']) ? (float)$b['en']['staleness_score'] : 0;
|
||||
return $scoreB <=> $scoreA;
|
||||
|
@ -279,7 +554,7 @@ class WikiController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/wiki/compare/{key}', name: 'app_admin_wiki_compare')]
|
||||
#[Route('/wiki/compare/{key}', name: 'app_admin_wiki_compare')]
|
||||
public function compare(string $key): Response
|
||||
{
|
||||
$csvFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/wiki_pages.csv';
|
||||
|
@ -323,10 +598,77 @@ class WikiController extends AbstractController
|
|||
|
||||
foreach ($jsonData as $page) {
|
||||
if ($page['key'] === $key) {
|
||||
$mediaComparison = $page['media_comparison'] ?? null;
|
||||
|
||||
// Deduplicate images by URL in the controller and filter out images that appear in both languages
|
||||
if ($mediaComparison) {
|
||||
// Get all image URLs from both languages
|
||||
$enOnlyImages = $mediaComparison['en_only'] ?? [];
|
||||
$frOnlyImages = $mediaComparison['fr_only'] ?? [];
|
||||
$commonImages = $mediaComparison['common'] ?? [];
|
||||
|
||||
// Extract all URLs from French images
|
||||
$frImageUrls = [];
|
||||
foreach ($frOnlyImages as $media) {
|
||||
$frImageUrls[] = $media['src'];
|
||||
}
|
||||
|
||||
// Also add URLs from common images (French side)
|
||||
foreach ($commonImages as $commonMedia) {
|
||||
if (isset($commonMedia['fr']['src'])) {
|
||||
$frImageUrls[] = $commonMedia['fr']['src'];
|
||||
}
|
||||
}
|
||||
|
||||
// Extract all URLs from English images
|
||||
$enImageUrls = [];
|
||||
foreach ($enOnlyImages as $media) {
|
||||
$enImageUrls[] = $media['src'];
|
||||
}
|
||||
|
||||
// Also add URLs from common images (English side)
|
||||
foreach ($commonImages as $commonMedia) {
|
||||
if (isset($commonMedia['en']['src'])) {
|
||||
$enImageUrls[] = $commonMedia['en']['src'];
|
||||
}
|
||||
}
|
||||
|
||||
// Process English-only images - deduplicate and filter out those that appear in French
|
||||
$enUniqueImages = [];
|
||||
$enProcessedUrls = [];
|
||||
|
||||
foreach ($enOnlyImages as $media) {
|
||||
// Skip if this URL is already processed or if it appears in French images
|
||||
if (!in_array($media['src'], $enProcessedUrls) && !in_array($media['src'], $frImageUrls)) {
|
||||
$enProcessedUrls[] = $media['src'];
|
||||
$enUniqueImages[] = $media;
|
||||
}
|
||||
}
|
||||
|
||||
// Process French-only images - deduplicate and filter out those that appear in English
|
||||
$frUniqueImages = [];
|
||||
$frProcessedUrls = [];
|
||||
|
||||
foreach ($frOnlyImages as $media) {
|
||||
// Skip if this URL is already processed or if it appears in English images
|
||||
if (!in_array($media['src'], $frProcessedUrls) && !in_array($media['src'], $enImageUrls)) {
|
||||
$frProcessedUrls[] = $media['src'];
|
||||
$frUniqueImages[] = $media;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the arrays with deduplicated and filtered versions
|
||||
$mediaComparison['en_only'] = $enUniqueImages;
|
||||
$mediaComparison['fr_only'] = $frUniqueImages;
|
||||
$mediaComparison['en_only_count'] = count($enOnlyImages);
|
||||
$mediaComparison['fr_only_count'] = count($frOnlyImages);
|
||||
}
|
||||
|
||||
$detailedComparison = [
|
||||
'section_comparison' => $page['section_comparison'] ?? null,
|
||||
'link_comparison' => $page['link_comparison'] ?? null,
|
||||
'media_comparison' => $page['media_comparison'] ?? null
|
||||
'media_comparison' => $mediaComparison,
|
||||
'category_comparison' => $page['category_comparison'] ?? null
|
||||
];
|
||||
|
||||
$mediaDiff = $page['media_diff'] ?? 0;
|
||||
|
@ -356,9 +698,9 @@ class WikiController extends AbstractController
|
|||
|
||||
// Calculate score components
|
||||
$dateComponent = abs($dateDiff) * 0.2;
|
||||
$wordComponent = abs($wordDiff) / 100 * 0.5;
|
||||
$wordComponent = (abs($wordDiff) / 100) * 0.5;
|
||||
$sectionComponent = abs($sectionDiff) * 0.15;
|
||||
$linkComponent = abs($linkDiff) / 10 * 0.15;
|
||||
$linkComponent = (abs($linkDiff) / 10) * 0.15;
|
||||
|
||||
$scoreComponents = [
|
||||
'date' => [
|
||||
|
@ -389,7 +731,7 @@ class WikiController extends AbstractController
|
|||
|
||||
// Add media component if available
|
||||
if (isset($enPage['media_count']) && isset($frPage['media_count'])) {
|
||||
$mediaComponent = abs($mediaDiff) / 5 * 0.1;
|
||||
$mediaComponent = (abs($mediaDiff) / 5) * 0.1;
|
||||
$scoreComponents['media'] = [
|
||||
'value' => $mediaDiff,
|
||||
'weight' => 0.1,
|
||||
|
|
28
src/Twig/AppExtension.php
Normal file
28
src/Twig/AppExtension.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
class AppExtension extends AbstractExtension
|
||||
{
|
||||
public function getFilters()
|
||||
{
|
||||
return [
|
||||
new TwigFilter('repeat', [$this, 'repeatFilter']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeats a string a specified number of times
|
||||
*
|
||||
* @param string $string The string to repeat
|
||||
* @param int $times The number of times to repeat the string
|
||||
* @return string The repeated string
|
||||
*/
|
||||
public function repeatFilter($string, $times)
|
||||
{
|
||||
return str_repeat($string, $times);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,16 @@
|
|||
<i class="bi bi-translate"></i> Pages sans traduction
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_pages_unavailable_in_french' %}active{% endif %}" href="{{ path('app_admin_wiki_pages_unavailable_in_french') }}">
|
||||
<i class="bi bi-globe"></i> Pages à traduire en français
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_osm_fr_groups' %}active{% endif %}" href="{{ path('app_admin_wiki_osm_fr_groups') }}">
|
||||
<i class="bi bi-people"></i> Groupes OSM-FR
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block title %}Pages Wiki OSM{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki OpenStreetMap</h1>
|
||||
|
@ -92,13 +92,16 @@
|
|||
</td>
|
||||
<td class="text-center">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ languages['en'].url }}" target="_blank" class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||
<a href="{{ languages['en'].url }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||
<i class="bi bi-translate"></i> EN
|
||||
</a>
|
||||
<a href="{{ languages['fr'].url }}" target="_blank" class="btn btn-sm btn-outline-info" title="Version française">
|
||||
<a href="{{ languages['fr'].url }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-info" title="Version française">
|
||||
<i class="bi bi-translate"></i> FR
|
||||
</a>
|
||||
<a href="{{ path('app_admin_wiki_compare', {'key': key}) }}" class="btn btn-sm btn-outline-secondary" title="Comparer les versions">
|
||||
<a href="{{ path('app_admin_wiki_compare', {'key': key}) }}"
|
||||
class="btn btn-sm btn-outline-secondary" title="Comparer les versions">
|
||||
<i class="bi bi-arrows-angle-expand"></i> Comparer
|
||||
</a>
|
||||
</div>
|
||||
|
@ -143,12 +146,20 @@
|
|||
</td>
|
||||
<td class="text-center">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.url }}" target="_blank" class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||
<i class="bi bi-translate"></i> EN
|
||||
<a href="{{ page.url }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-info" title="Version anglaise">
|
||||
<i class="bi bi-flag-fill"></i> EN
|
||||
</a>
|
||||
<a href="{{ path('app_admin_wiki_compare', {'key': key}) }}" class="btn btn-sm btn-outline-secondary" title="Voir les détails et créer la page française">
|
||||
<i class="bi bi-arrows-angle-expand"></i> Comparer
|
||||
<a href="https://wiki.openstreetmap.org/w/index.php?title=FR:{{ key }}&action=edit"
|
||||
target="_blank"
|
||||
class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||
<i class="bi bi-translate"></i> créer FR
|
||||
</a>
|
||||
{# <a href="{{ path('app_admin_wiki_compare', {'key': key}) }}" #}
|
||||
{# class="btn btn-sm btn-outline-secondary" #}
|
||||
{# title="Voir les détails et créer la page française"> #}
|
||||
{# <i class="bi bi-arrows-angle-expand"></i> Comparer #}
|
||||
{# </a> #}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -160,11 +171,12 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<p>
|
||||
le score de fraîcheur prend en compte d'avantage la différence entre le nombre de mots que l'ancienneté de modification.
|
||||
le score de fraîcheur prend en compte d'avantage la différence entre le nombre de mots que l'ancienneté de
|
||||
modification.
|
||||
On compte aussi le nombre de sections et de liens.
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
File diff suppressed because it is too large
Load diff
|
@ -9,38 +9,38 @@
|
|||
<h1>Pages Wiki françaises sans traduction anglaise</h1>
|
||||
<p class="lead">Liste des pages françaises du wiki OSM qui n'ont pas de traduction en anglais.</p>
|
||||
|
||||
{% if last_updated %}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> Dernière mise à jour : {{ last_updated|date('d/m/Y H:i') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Pages françaises uniquement</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if french_only_pages|length > 0 %}
|
||||
{% if untranslated_pages|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Clé</th>
|
||||
<th>Sections</th>
|
||||
<th>Mots</th>
|
||||
<th>Liens</th>
|
||||
<th>Dernière modification</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key, page in french_only_pages %}
|
||||
{% for page in untranslated_pages %}
|
||||
<tr>
|
||||
<td><strong>{{ key }}</strong></td>
|
||||
<td>{{ page.sections }}</td>
|
||||
<td>{{ page.word_count }}</td>
|
||||
<td>{{ page.link_count }}</td>
|
||||
<td>{{ page.last_modified }}</td>
|
||||
<td><strong>{{ page.title }}</strong></td>
|
||||
<td>{{ page.key }}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.url }}" target="_blank" class="btn btn-sm btn-outline-info" title="Version française">
|
||||
<i class="bi bi-translate"></i> FR
|
||||
</a>
|
||||
<a href="https://wiki.openstreetmap.org/wiki/Key:{{ key }}" target="_blank" class="btn btn-sm btn-success" title="Créer la version anglaise">
|
||||
<a href="https://wiki.openstreetmap.org/wiki/{{ page.key }}" target="_blank" class="btn btn-sm btn-success" title="Créer la version anglaise">
|
||||
<i class="bi bi-plus-circle"></i> Créer EN
|
||||
</a>
|
||||
</div>
|
||||
|
|
161
templates/admin/wiki_osm_fr_groups.html.twig
Normal file
161
templates/admin/wiki_osm_fr_groups.html.twig
Normal file
|
@ -0,0 +1,161 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Groupes OSM-FR{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Groupes OSM-FR</h1>
|
||||
<p class="lead">Liste des groupes de travail et des groupes locaux d'OpenStreetMap France.</p>
|
||||
|
||||
{% if last_updated %}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> Dernière mise à jour : {{ last_updated|date('d/m/Y H:i') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Carte uMap des groupes locaux -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2>
|
||||
<a href="{{ umap_url }}">
|
||||
|
||||
Carte des groupes locaux
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="ratio ratio-16x9">
|
||||
<iframe src="{{ umap_url }}" frameborder="0"></iframe>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<a href="{{ umap_url }}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir la carte en plein écran
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Groupes de travail -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h2>Groupes de travail</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if working_groups|length > 0 %}
|
||||
<div class="accordion" id="workingGroupsAccordion">
|
||||
{% for category, groups in working_groups %}
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading{{ loop.index }}">
|
||||
<button class="accordion-button {% if not loop.first %}collapsed{% endif %}"
|
||||
type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse{{ loop.index }}"
|
||||
aria-expanded="{{ loop.first ? 'true' : 'false' }}"
|
||||
aria-controls="collapse{{ loop.index }}">
|
||||
{{ category }} ({{ groups|length }})
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse{{ loop.index }}"
|
||||
class="accordion-collapse collapse {% if loop.first %}show{% endif %}"
|
||||
aria-labelledby="heading{{ loop.index }}" data-bs-parent="#workingGroupsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="list-group">
|
||||
{% for group in groups %}
|
||||
<a href="{{ group.url }}" target="_blank"
|
||||
class="list-group-item list-group-item-action">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">{{ group.name }}</h5>
|
||||
</div>
|
||||
{% if group.description %}
|
||||
<p class="mb-1">{{ group.description }}</p>
|
||||
{% endif %}
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir sur le wiki
|
||||
</small>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucun groupe de travail n'a été trouvé.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Groupes locaux -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h2>Groupes locaux</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if local_groups|length > 0 %}
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
{% for group in local_groups %}
|
||||
<div class="col">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ group.name }}</h5>
|
||||
{% if group.description %}
|
||||
<p class="card-text">{{ group.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{{ group.url }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir sur le wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucun groupe local n'a été trouvé.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>À propos des groupes OSM-FR</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5>Groupes de travail</h5>
|
||||
<p>Les groupes de travail sont des équipes thématiques qui se concentrent sur des aspects spécifiques
|
||||
d'OpenStreetMap en France. Ils permettent de coordonner les efforts sur des sujets particuliers
|
||||
comme l'import de données, la cartographie des transports, etc.</p>
|
||||
|
||||
<h5>Groupes locaux</h5>
|
||||
<p>Les groupes locaux sont des communautés géographiques de contributeurs OpenStreetMap. Ils organisent
|
||||
des rencontres, des ateliers de cartographie et d'autres événements pour promouvoir OSM dans leur
|
||||
région.</p>
|
||||
|
||||
<div class="d-grid gap-2 col-md-6 mx-auto mt-3">
|
||||
<a href="https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail" target="_blank"
|
||||
class="btn btn-outline-success">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir tous les groupes de travail sur le wiki
|
||||
</a>
|
||||
<a href="https://wiki.openstreetmap.org/wiki/France/OSM-FR#Groupes_locaux" target="_blank"
|
||||
class="btn btn-outline-info">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir tous les groupes locaux sur le wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ path('app_admin_wiki') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Retour à la liste des pages wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
130
templates/admin/wiki_pages_unavailable_in_french.html.twig
Normal file
130
templates/admin/wiki_pages_unavailable_in_french.html.twig
Normal file
|
@ -0,0 +1,130 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Pages Wiki non disponibles en français{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki non disponibles en français</h1>
|
||||
<p class="lead">Liste des pages du wiki OSM qui n'ont pas de traduction française, groupées par langue d'origine.</p>
|
||||
|
||||
{% if last_updated %}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> Dernière mise à jour : {{ last_updated|date('d/m/Y H:i') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Statistiques</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-primary mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Total des pages</h5>
|
||||
<p class="card-text display-4">{{ all_pages|length }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-success mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Pages en anglais</h5>
|
||||
<p class="card-text display-4">{{ grouped_pages['En']|default([])|length }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-info mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Langues différentes</h5>
|
||||
<p class="card-text display-4">{{ grouped_pages|length }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pages groupées par langue -->
|
||||
<div class="accordion" id="languageAccordion">
|
||||
{% for lang_prefix, pages in grouped_pages %}
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading{{ lang_prefix }}">
|
||||
<button class="accordion-button {% if lang_prefix != 'En' %}collapsed{% endif %}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{ lang_prefix }}" aria-expanded="{{ lang_prefix == 'En' ? 'true' : 'false' }}" aria-controls="collapse{{ lang_prefix }}">
|
||||
{% if lang_prefix == 'En' %}
|
||||
<strong class="text-success">Pages en anglais ({{ pages|length }})</strong>
|
||||
{% elseif lang_prefix == 'Other' %}
|
||||
<strong>Autres pages ({{ pages|length }})</strong>
|
||||
{% else %}
|
||||
<strong>Pages en {{ lang_prefix }} ({{ pages|length }})</strong>
|
||||
{% endif %}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse{{ lang_prefix }}" class="accordion-collapse collapse {% if lang_prefix == 'En' %}show{% endif %}" aria-labelledby="heading{{ lang_prefix }}" data-bs-parent="#languageAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in pages %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ page.title }}</strong>
|
||||
{% if page.is_english %}
|
||||
<span class="badge bg-success">Priorité</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.url }}" target="_blank" class="btn btn-sm btn-outline-primary" title="Voir la page originale">
|
||||
<i class="bi bi-eye"></i> Voir
|
||||
</a>
|
||||
{% set fr_url = page.url|replace({'/wiki/': '/wiki/FR:'}) %}
|
||||
<a href="{{ fr_url }}" target="_blank" class="btn btn-sm btn-success" title="Créer la traduction française">
|
||||
<i class="bi bi-plus-circle"></i> Traduire
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="card mt-4 mb-4">
|
||||
<div class="card-header">
|
||||
<h2>À propos des pages non disponibles en français</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Ces pages sont des contenus du wiki OpenStreetMap qui n'ont pas encore été traduits en français.</p>
|
||||
<p>Contribuer à la traduction de ces pages permet de :</p>
|
||||
<ul>
|
||||
<li>Rendre la documentation OSM plus accessible aux contributeurs francophones</li>
|
||||
<li>Améliorer la qualité des contributions en français</li>
|
||||
<li>Faciliter l'apprentissage et l'utilisation d'OpenStreetMap pour les nouveaux utilisateurs</li>
|
||||
</ul>
|
||||
<p><strong>Priorité aux pages anglaises :</strong> Les pages commençant par "En:" sont prioritaires car l'anglais est la langue principale du wiki OSM.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ path('app_admin_wiki') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Retour à la liste des pages wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -3,56 +3,66 @@
|
|||
{% block title %}Pages Wiki avec suppressions suspectes{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki avec suppressions suspectes</h1>
|
||||
<p class="lead">Pages françaises du wiki OSM qui ont des suppressions suspectes, avec un grand pourcentage de suppression par rapport à la version anglaise.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Cette page présente deux types de suppressions suspectes :
|
||||
<ul>
|
||||
<li><strong>Suppressions récentes</strong> : Détectées en temps réel dans les changements récents du
|
||||
wiki (suppressions > 20 caractères)
|
||||
</li>
|
||||
<li><strong>Différences de contenu</strong> : Pages françaises contenant significativement moins de mots
|
||||
que leurs équivalents anglais
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Suppressions récentes -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Pages avec suppressions suspectes</h2>
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h2>Suppressions récentes suspectes</h2>
|
||||
{% if last_updated %}
|
||||
<small>Dernière mise à jour : {{ last_updated|date('d/m/Y H:i') }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if suspicious_pages|length > 0 %}
|
||||
{% if recent_deletions is defined and recent_deletions|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Clé</th>
|
||||
<th>Mots (EN)</th>
|
||||
<th>Mots (FR)</th>
|
||||
<th>Différence</th>
|
||||
<th>% Suppression</th>
|
||||
<th>Page</th>
|
||||
<th>Suppression</th>
|
||||
<th>Date</th>
|
||||
<th>Utilisateur</th>
|
||||
<th>Commentaire</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in suspicious_pages %}
|
||||
{% for deletion in recent_deletions %}
|
||||
<tr>
|
||||
<td><strong>{{ page.key }}</strong></td>
|
||||
<td>{{ page.en_page.word_count }}</td>
|
||||
<td>{{ page.fr_page.word_count }}</td>
|
||||
<td>{{ page.en_page.word_count - page.fr_page.word_count }}</td>
|
||||
<td><strong>{{ deletion.page_title }}</strong></td>
|
||||
<td>
|
||||
{% if page.deletion_percentage > 70 %}
|
||||
<span class="badge bg-danger">{{ page.deletion_percentage }}%</span>
|
||||
{% elseif page.deletion_percentage > 50 %}
|
||||
<span class="badge bg-warning text-dark">{{ page.deletion_percentage }}%</span>
|
||||
{% else %}
|
||||
<span class="badge bg-info">{{ page.deletion_percentage }}%</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-danger">{{ deletion.deletion_size }} caractères</span>
|
||||
</td>
|
||||
<td>{{ deletion.timestamp }}</td>
|
||||
<td>{{ deletion.user }}</td>
|
||||
<td>{{ deletion.comment }}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.en_page.url }}" target="_blank" class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||
<i class="bi bi-translate"></i> EN
|
||||
<a href="{{ deletion.page_url }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-primary" title="Voir la page">
|
||||
<i class="bi bi-eye"></i> Voir
|
||||
</a>
|
||||
<a href="{{ page.fr_page.url }}" target="_blank" class="btn btn-sm btn-outline-info" title="Version française">
|
||||
<i class="bi bi-translate"></i> FR
|
||||
</a>
|
||||
<a href="{{ path('app_admin_wiki_compare', {'key': page.key}) }}" class="btn btn-sm btn-outline-secondary" title="Comparer les versions">
|
||||
<i class="bi bi-arrows-angle-expand"></i> Comparer
|
||||
<a href="https://wiki.openstreetmap.org/w/index.php?title={{ deletion.page_title|url_encode }}&action=history"
|
||||
target="_blank" class="btn btn-sm btn-outline-secondary"
|
||||
title="Historique">
|
||||
<i class="bi bi-clock-history"></i> Historique
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -61,25 +71,42 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<a href="https://wiki.openstreetmap.org/w/index.php?hidebots=1&hidenewpages=1&hidecategorization=1&hideWikibase=1&hidelog=1&hidenewuserlog=1&namespace=202&limit=250&days=30&enhanced=1&title=Special:RecentChanges&urlversion=2"
|
||||
target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-arrow-right-circle"></i> Voir tous les changements récents sur le wiki
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucune page avec suppressions suspectes n'a été trouvée.</p>
|
||||
<p><i class="bi bi-info-circle"></i> Aucune suppression suspecte récente n'a été détectée.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Différences de contenu -->
|
||||
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>À propos des suppressions suspectes</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Les suppressions suspectes sont identifiées lorsque la version française d'une page wiki contient significativement moins de mots que la version anglaise (plus de 30% de différence).</p>
|
||||
<h5>Suppressions récentes</h5>
|
||||
<p>Les suppressions récentes sont détectées en analysant les changements récents du wiki OSM. Toute
|
||||
suppression de plus de 20 caractères est considérée comme potentiellement suspecte et mérite une
|
||||
vérification.</p>
|
||||
|
||||
<h5>Différences de contenu</h5>
|
||||
<p>Les différences de contenu sont identifiées lorsque la version française d'une page wiki contient
|
||||
significativement moins de mots que la version anglaise (plus de 30% de différence).</p>
|
||||
<p>Cela peut indiquer :</p>
|
||||
<ul>
|
||||
<li>Une traduction incomplète</li>
|
||||
<li>Des sections manquantes dans la version française</li>
|
||||
<li>Des mises à jour importantes dans la version anglaise qui n'ont pas été reportées en français</li>
|
||||
<li>Des mises à jour importantes dans la version anglaise qui n'ont pas été reportées en français
|
||||
</li>
|
||||
</ul>
|
||||
<p>Ces pages sont des candidates prioritaires pour une mise à jour de la traduction française.</p>
|
||||
</div>
|
||||
|
@ -90,5 +117,5 @@
|
|||
<i class="bi bi-arrow-left"></i> Retour à la liste des pages wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -6,42 +6,40 @@
|
|||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Propositions de tags OSM en cours de vote</h1>
|
||||
<p class="lead">Liste des propositions de tags OpenStreetMap actuellement en cours de vote ou de discussion.</p>
|
||||
<h1>Propositions de tags OSM</h1>
|
||||
<p class="lead">Liste des propositions de tags OpenStreetMap actuellement en cours de vote ou récemment modifiées.</p>
|
||||
|
||||
{% if last_updated %}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> Dernière mise à jour : {{ last_updated|date('d/m/Y H:i') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Propositions en cours de vote -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Propositions actives</h2>
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2>Propositions en cours de vote</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if proposals|length > 0 %}
|
||||
{% set voting_proposals = proposals|filter(p => p.type == 'voting') %}
|
||||
{% if voting_proposals|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Fonctionnalité</th>
|
||||
<th>Proposition</th>
|
||||
<th>Description</th>
|
||||
<th>Proposé par</th>
|
||||
<th>Statut</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for proposal in proposals %}
|
||||
{% for proposal in voting_proposals %}
|
||||
<tr>
|
||||
<td>{{ proposal.feature }}</td>
|
||||
<td><strong>{{ proposal.feature }}</strong></td>
|
||||
<td>{{ proposal.description }}</td>
|
||||
<td>{{ proposal.proposer }}</td>
|
||||
<td>
|
||||
{% if 'voting' in proposal.status|lower %}
|
||||
<span class="badge bg-primary">En vote</span>
|
||||
{% elseif 'draft' in proposal.status|lower %}
|
||||
<span class="badge bg-warning text-dark">Brouillon</span>
|
||||
{% elseif 'rfc' in proposal.status|lower %}
|
||||
<span class="badge bg-info">RFC</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ proposal.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ proposal.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||
|
@ -55,7 +53,49 @@
|
|||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucune proposition de tag n'a été trouvée.</p>
|
||||
<p><i class="bi bi-info-circle"></i> Aucune proposition en cours de vote n'a été trouvée.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Propositions récemment modifiées -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h2>Propositions récemment modifiées</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% set recent_proposals = proposals|filter(p => p.type == 'recent') %}
|
||||
{% if recent_proposals|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Proposition</th>
|
||||
<th>Dernière modification</th>
|
||||
<th>Modifié par</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for proposal in recent_proposals %}
|
||||
<tr>
|
||||
<td><strong>{{ proposal.feature }}</strong></td>
|
||||
<td>{{ proposal.description }}</td>
|
||||
<td>{{ proposal.proposer }}</td>
|
||||
<td>
|
||||
<a href="{{ proposal.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucune proposition récemment modifiée n'a été trouvée.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue