Changeset 3763820
- Timestamp:
- 10/24/2026 11:14:30 PM (10 hours ago)
- Location:
- filikod
- Files:
-
- 62 added
- 17 edited
-
tags/1.2.1/readme.txt (modified) (3 diffs)
-
tags/1.3.0 (added)
-
tags/1.3.0/admin (added)
-
tags/1.3.0/admin/index.php (added)
-
tags/1.3.0/admin/views (added)
-
tags/1.3.0/admin/views/alt-audit.php (added)
-
tags/1.3.0/admin/views/dashboard.php (added)
-
tags/1.3.0/admin/views/duplicates-similar.php (added)
-
tags/1.3.0/admin/views/duplicates.php (added)
-
tags/1.3.0/admin/views/index.php (added)
-
tags/1.3.0/admin/views/settings.php (added)
-
tags/1.3.0/assets (added)
-
tags/1.3.0/assets/css (added)
-
tags/1.3.0/assets/css/admin.css (added)
-
tags/1.3.0/assets/css/index.php (added)
-
tags/1.3.0/assets/css/media-modal-alt-status.css (added)
-
tags/1.3.0/assets/filikod-picto.svg (added)
-
tags/1.3.0/assets/index.php (added)
-
tags/1.3.0/assets/js (added)
-
tags/1.3.0/assets/js/admin.js (added)
-
tags/1.3.0/assets/js/index.php (added)
-
tags/1.3.0/assets/js/media-alt-badges.js (added)
-
tags/1.3.0/assets/js/media-duplicate-alert.js (added)
-
tags/1.3.0/filikod.php (added)
-
tags/1.3.0/includes (added)
-
tags/1.3.0/includes/accessibility (added)
-
tags/1.3.0/includes/accessibility/class-filikod-accessibility.php (added)
-
tags/1.3.0/includes/accessibility/index.php (added)
-
tags/1.3.0/includes/admin (added)
-
tags/1.3.0/includes/admin/class-filikod-admin.php (added)
-
tags/1.3.0/includes/admin/class-filikod-media-alt-status.php (added)
-
tags/1.3.0/includes/admin/index.php (added)
-
tags/1.3.0/includes/class-filikod-alt-audit.php (added)
-
tags/1.3.0/includes/class-filikod-media-utils.php (added)
-
tags/1.3.0/includes/dashboard (added)
-
tags/1.3.0/includes/dashboard/class-filikod-dashboard.php (added)
-
tags/1.3.0/includes/dashboard/index.php (added)
-
tags/1.3.0/includes/duplicates (added)
-
tags/1.3.0/includes/duplicates/class-filikod-duplicates.php (added)
-
tags/1.3.0/includes/duplicates/class-filikod-visual-duplicates.php (added)
-
tags/1.3.0/includes/duplicates/index.php (added)
-
tags/1.3.0/includes/file-types (added)
-
tags/1.3.0/includes/file-types/class-filikod-file-types.php (added)
-
tags/1.3.0/includes/file-types/index.php (added)
-
tags/1.3.0/includes/index.php (added)
-
tags/1.3.0/includes/optimizations (added)
-
tags/1.3.0/includes/optimizations/class-filikod-image-resizer.php (added)
-
tags/1.3.0/includes/optimizations/index.php (added)
-
tags/1.3.0/includes/security (added)
-
tags/1.3.0/includes/security/class-filikod-svg-security.php (added)
-
tags/1.3.0/includes/security/index.php (added)
-
tags/1.3.0/includes/settings (added)
-
tags/1.3.0/includes/settings/class-filikod-settings.php (added)
-
tags/1.3.0/includes/settings/index.php (added)
-
tags/1.3.0/index.php (added)
-
tags/1.3.0/languages (added)
-
tags/1.3.0/languages/filikod-fr_FR.mo (added)
-
tags/1.3.0/languages/filikod-fr_FR.po (added)
-
tags/1.3.0/languages/filikod.pot (added)
-
tags/1.3.0/readme.txt (added)
-
tags/1.3.0/uninstall.php (added)
-
trunk/admin/views/alt-audit.php (modified) (3 diffs)
-
trunk/admin/views/duplicates-similar.php (added)
-
trunk/admin/views/duplicates.php (modified) (7 diffs)
-
trunk/assets/css/admin.css (modified) (3 diffs)
-
trunk/assets/js/admin.js (modified) (1 diff)
-
trunk/filikod.php (modified) (7 diffs)
-
trunk/includes/accessibility/class-filikod-accessibility.php (modified) (6 diffs)
-
trunk/includes/admin/class-filikod-admin.php (modified) (5 diffs)
-
trunk/includes/admin/class-filikod-media-alt-status.php (modified) (1 diff)
-
trunk/includes/class-filikod-alt-audit.php (modified) (16 diffs)
-
trunk/includes/dashboard/class-filikod-dashboard.php (modified) (1 diff)
-
trunk/includes/duplicates/class-filikod-duplicates.php (modified) (20 diffs)
-
trunk/includes/duplicates/class-filikod-visual-duplicates.php (added)
-
trunk/languages/filikod-fr_FR.mo (modified) ( previous)
-
trunk/languages/filikod-fr_FR.po (modified) (14 diffs)
-
trunk/languages/filikod.pot (modified) (14 diffs)
-
trunk/readme.txt (modified) (8 diffs)
-
trunk/uninstall.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
filikod/tags/1.2.1/readme.txt
r3737019 r3763820 38 38 39 39 * **SEO professionals** who want ALT text treated as a measurable image SEO signal, not an afterthought 40 * **Accessibility-focused teams** who need a structured audit to meet WCAG 2. 1success criterion 1.1.1 (non-text content)40 * **Accessibility-focused teams** who need a structured audit to meet WCAG 2.2 success criterion 1.1.1 (non-text content) 41 41 * **Content managers** with large media libraries who need to fix issues fast, in bulk 42 42 * **Agencies** managing multiple WordPress sites who need a repeatable quality process … … 57 57 58 58 This turns ALT text from a vague, invisible problem into a measurable system you can actually improve, like a health score for your media library. 59 60 --- 61 62 = Accessibility Standards: WCAG 2.2, ADA, Section 508, EAA, RGAA = 63 64 Every major accessibility standard starts with the same rule for images: WCAG success criterion 1.1.1 (Non-text Content, Level A). An image that carries information needs a text alternative. A purely decorative image needs an empty one (`alt=""`) so screen readers skip it. 65 66 That rule sits behind the laws and frameworks your clients ask about: 67 68 * **WCAG 2.2** (and 2.1, 2.0): success criterion 1.1.1, unchanged since 2008 69 * **ADA** (United States): the Title II rule for state and local governments references WCAG 2.1 AA, and WCAG is the usual benchmark in ADA website cases 70 * **Section 508** (US federal websites): WCAG 2.0 AA 71 * **European Accessibility Act (EAA)**: applies since June 28, 2025, through EN 301 549, which references WCAG 2.1 AA 72 * **RGAA** (France): theme 1 "Images", criteria 1.1 to 1.9 73 74 Filikod checks the image part of these standards across your whole media library: images with no text alternative, generic or filename-like ALT text that a screen reader reads out as noise ("IMG_4832", "photo"), and images you mark as **decorative** so they keep an intentional empty `alt=""`. WooCommerce product images live in the media library too, so they are audited like any other image. 75 76 Filikod covers this one criterion, not a whole standard. It does not replace a full accessibility audit, and no plugin can make a site compliant on its own. What it gives you is a measured starting point for one of the most frequent failures on the web. 59 77 60 78 --- … … 183 201 184 202 A checker first. Filikod audits the alt text that already exists in your media library and scores it. It never sends your images to an external AI service and never invents descriptions behind your back. An optional, local filename-based setting (off by default) can fill a starting ALT for images that have none, and you then review it in the ALT Audit like any other ALT text. 203 204 = Does Filikod make my site WCAG, ADA or EAA compliant? = 205 206 No plugin can do that on its own. Filikod covers the image part of these standards (WCAG success criterion 1.1.1, RGAA theme 1): it finds images without a proper text alternative, lets you fix them in bulk and keeps decorative images with an intentional empty `alt=""`. Color contrast, keyboard navigation, forms and the rest of WCAG need their own checks. 185 207 186 208 = Does Filikod generate ALT text automatically? = -
filikod/trunk/admin/views/alt-audit.php
r3737001 r3763820 98 98 ); 99 99 $filikod_save_alt_nonce = wp_create_nonce( 'filikod_save_alt' ); 100 101 // Colonne Langue : seulement quand un plugin multilingue donne une langue aux médias (Polylang, WPML, Bogo) 102 // ou écrit plusieurs langues dans le même ALT (WPGlobus, qTranslate-XT). 103 $filikod_languages = ( class_exists( 'Filikod_Duplicates' ) && '' !== Filikod_Duplicates::get_multilingual_engine() ) 104 ? Filikod_Duplicates::get_language_map() 105 : null; 106 $filikod_alt_lang_engine = class_exists( 'Filikod_Alt_Audit' ) ? Filikod_Alt_Audit::get_alt_language_engine() : ''; 107 $filikod_show_language = is_array( $filikod_languages ) || '' !== $filikod_alt_lang_engine; 108 if ( 'missing' === $status && '' !== $filikod_alt_lang_engine ) { 109 $filikod_issue_descriptions['missing'] .= ' ' . __( 'With WPGlobus or qTranslate-XT, an image is also listed here when its ALT is empty in one of the site languages (see the Language column).', 'filikod' ); 110 } 100 111 ?> 101 112 <script type="text/javascript"> … … 152 163 <th class="column-thumbnail"><?php esc_html_e( 'Thumbnail', 'filikod' ); ?></th> 153 164 <th class="column-filename"><?php esc_html_e( 'File name', 'filikod' ); ?></th> 165 <?php if ( $filikod_show_language ) : ?> 166 <th class="column-language"><?php esc_html_e( 'Language', 'filikod' ); ?></th> 167 <?php endif; ?> 154 168 <th class="column-uploaded-to"><?php esc_html_e( 'Context', 'filikod' ); ?></th> 155 169 <th class="column-alt"><?php esc_html_e( 'ALT', 'filikod' ); ?></th> … … 204 218 </td> 205 219 <td class="column-filename"><?php echo esc_html( $filikod_file_name ); ?></td> 220 <?php if ( $filikod_show_language ) : ?> 221 <td class="column-language"> 222 <?php 223 $filikod_alt_langs = isset( $filikod_item['languages'] ) && is_array( $filikod_item['languages'] ) ? $filikod_item['languages'] : null; 224 if ( null !== $filikod_alt_langs ) { 225 // ALT multilingue (WPGlobus, qTranslate-XT) : langues remplies, puis langues vides. 226 echo esc_html( $filikod_alt_langs['present'] ? strtoupper( implode( ' · ', $filikod_alt_langs['present'] ) ) : '–' ); 227 if ( ! empty( $filikod_alt_langs['missing'] ) ) { 228 echo '<span class="filikod-lang-missing">' . esc_html( 229 sprintf( 230 /* translators: %s: language codes, e.g. "EN, DE" */ 231 __( 'empty: %s', 'filikod' ), 232 strtoupper( implode( ', ', $filikod_alt_langs['missing'] ) ) 233 ) 234 ) . '</span>'; 235 } 236 } elseif ( isset( $filikod_languages[ $filikod_attachment_id ] ) ) { 237 echo esc_html( strtoupper( $filikod_languages[ $filikod_attachment_id ] ) ); 238 } elseif ( '' !== $filikod_alt_lang_engine && '' !== trim( $filikod_alt_raw ) ) { 239 // ALT sans balise de langue : le même texte sert toutes les langues. 240 echo '<span class="filikod-lang-untranslated">' . esc_html__( 'Not translated', 'filikod' ) . '</span>'; 241 } else { 242 echo '–'; 243 } 244 ?> 245 </td> 246 <?php endif; ?> 206 247 <td class="column-uploaded-to"><?php echo wp_kses_post( $filikod_uploaded_to ); ?></td> 207 248 <td class="column-alt"> -
filikod/trunk/admin/views/duplicates.php
r3737001 r3763820 17 17 18 18 $filikod_paged = isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only pagination parameter, cast to int and clamped. 19 $filikod_view = isset( $_GET['view'] ) && 'missing' === sanitize_text_field( wp_unslash( $_GET['view'] ) ) ? 'missing' : 'duplicates'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only tab parameter, strictly whitelisted. 19 $filikod_view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only tab parameter, strictly whitelisted below. 20 $filikod_view = in_array( $filikod_view, array( 'missing', 'similar' ), true ) ? $filikod_view : 'duplicates'; 21 $filikod_filter = isset( $_GET['filter'] ) ? sanitize_key( wp_unslash( $_GET['filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only sub-tab parameter, strictly whitelisted below. 22 $filikod_filter = in_array( $filikod_filter, array( 'languages', 'ignored' ), true ) ? $filikod_filter : 'same'; 20 23 $filikod_base_url = add_query_arg( array( 'page' => 'filikod-duplicates' ), admin_url( 'admin.php' ) ); 21 24 $filikod_missing_url = add_query_arg( array( 'view' => 'missing' ), $filikod_base_url ); 25 $filikod_similar_url = add_query_arg( array( 'view' => 'similar' ), $filikod_base_url ); 26 27 $filikod_visual_counts = Filikod_Visual_Duplicates::get_counts(); 28 $filikod_visual_signatures = Filikod_Visual_Duplicates::get_signature_counts(); 29 $filikod_similar_data = array( 'groups' => array(), 'total' => 0, 'total_pages' => 0, 'paged' => 1 ); 30 if ( 'similar' === $filikod_view ) { 31 $filikod_similar_data = Filikod_Visual_Duplicates::get_groups_paginated( $filikod_filter, $filikod_paged ); 32 } 22 33 23 34 $filikod_summary = Filikod_Duplicates::get_summary(); … … 116 127 ); 117 128 } 129 $filikod_visual_waiting = max( 0, (int) $filikod_summary['total'] - $filikod_visual_signatures['signed'] - $filikod_visual_signatures['none'] ); 130 if ( $filikod_visual_waiting > 0 && Filikod_Visual_Duplicates::is_supported() ) { 131 echo ' '; 132 printf( 133 /* translators: %s: number of images without a visual signature */ 134 esc_html__( '%s images have no visual signature yet (Similar images tab): "Scan library" computes them.', 'filikod' ), 135 esc_html( number_format_i18n( $filikod_visual_waiting, 0 ) ) 136 ); 137 } 118 138 ?> 119 139 </p> … … 146 166 <?php esc_html_e( 'Duplicated files', 'filikod' ); ?> (<span class="filikod-alt-tab-count"><?php echo esc_html( number_format_i18n( (int) $filikod_summary['redundant'], 0 ) ); ?></span>) 147 167 </a> 168 <a href="<?php echo esc_url( $filikod_similar_url ); ?>" class="filikod-tab <?php echo 'similar' === $filikod_view ? 'active' : ''; ?>"> 169 <?php esc_html_e( 'Similar images', 'filikod' ); ?> (<span class="filikod-alt-tab-count"><?php echo esc_html( number_format_i18n( (int) $filikod_visual_counts['same'], 0 ) ); ?></span>) 170 </a> 148 171 <a href="<?php echo esc_url( $filikod_missing_url ); ?>" class="filikod-tab <?php echo 'missing' === $filikod_view ? 'active' : ''; ?>"> 149 172 <?php esc_html_e( 'Missing files', 'filikod' ); ?> (<span class="filikod-alt-tab-count"><?php echo esc_html( number_format_i18n( $filikod_unhashable, 0 ) ); ?></span>) … … 153 176 <div class="filikod-tabs-content"> 154 177 155 <?php if ( 'missing' === $filikod_view ) : ?> 178 <?php if ( 'similar' === $filikod_view ) : ?> 179 <?php include FILIKOD_PLUGIN_PATH . 'admin/views/duplicates-similar.php'; ?> 180 <?php elseif ( 'missing' === $filikod_view ) : ?> 156 181 <!-- Onglet Missing files --> 157 182 <div id="tab-missing" class="filikod-tab-content active"> … … 228 253 /* translators: 1: current page, 2: total pages, 3: total items */ 229 254 esc_html__( 'Page %1$d of %2$d (%3$d items)', 'filikod' ), 230 $filikod_m_current,231 $filikod_m_pages,255 (int) $filikod_m_current, 256 (int) $filikod_m_pages, 232 257 (int) $filikod_missing_data['total'] 233 258 ); … … 251 276 <div id="tab-duplicates" class="filikod-tab-content active"> 252 277 <p class="filikod-alt-audit-description"> 253 <?php esc_html_e( 'Exact copies of the same file (same md5 fingerprint) uploaded more than once. Nothing is deleted or merged: check where each copy is used, keep that one, and remove the others from the Media Library once you are sure. Media linked as translations of each other (Polylang, WPML, Bogo) are intentional and never listed here.', 'filikod' ); ?> 278 <?php 279 esc_html_e( 'Exact copies of the same file (same md5 fingerprint) uploaded more than once. Nothing is deleted or merged: check where each copy is used, keep that one, and remove the others from the Media Library once you are sure.', 'filikod' ); 280 // Phrase sur les traductions seulement quand un plugin multilingue gère la médiathèque. 281 if ( '' !== Filikod_Duplicates::get_multilingual_engine() ) { 282 echo ' '; 283 esc_html_e( 'Media linked as translations of each other (Polylang, WPML, Bogo) are intentional and never listed here.', 'filikod' ); 284 } 285 ?> 254 286 </p> 255 287 … … 379 411 /* translators: 1: current page, 2: total pages, 3: total groups */ 380 412 esc_html__( 'Page %1$d of %2$d (%3$d groups)', 'filikod' ), 381 $filikod_current,382 $filikod_total_pages,413 (int) $filikod_current, 414 (int) $filikod_total_pages, 383 415 (int) $filikod_page_data['total'] 384 416 ); -
filikod/trunk/assets/css/admin.css
r3737001 r3763820 3 3 */ 4 4 5 /* Icône du menu - S'assurer que le SVG utilise la couleur du thème WordPress */ 6 #toplevel_page_filikod .wp-menu-image:before { 7 content: ''; 8 display: none; 9 } 10 11 #toplevel_page_filikod .wp-menu-image { 12 display: flex; 13 align-items: center; 14 justify-content: center; 15 width: 20px; 16 height: 20px; 17 color: #a7aaad; /* Gris par défaut */ 18 } 19 20 #toplevel_page_filikod .wp-menu-image img, 21 #toplevel_page_filikod .wp-menu-image svg { 22 width: 20px; 23 height: 20px; 24 display: block; 25 } 26 27 #toplevel_page_filikod .wp-menu-image svg { 28 fill: currentColor; 29 } 30 31 /* Blanc quand actif ou en hover */ 32 #toplevel_page_filikod:hover .wp-menu-image, 33 #toplevel_page_filikod.current .wp-menu-image, 34 #toplevel_page_filikod.wp-has-current-submenu .wp-menu-image { 35 color: #fff; 36 } 5 /* Icône du menu : SVG passé à add_menu_page() et recoloré par WordPress, aucune règle ici. */ 37 6 38 7 /* Dashboard */ … … 1237 1206 } 1238 1207 1208 /* Colonne Langue (sites multilingues seulement) : code court, « FR », « EN » */ 1209 .filikod-alt-audit-table .column-language { 1210 width: 90px; 1211 } 1212 1213 .filikod-alt-audit-table .column-language .filikod-lang-missing { 1214 display: block; 1215 margin-top: 2px; 1216 font-size: 12px; 1217 color: #b32d2e; 1218 } 1219 1220 .filikod-alt-audit-table .column-language .filikod-lang-untranslated { 1221 font-size: 12px; 1222 color: #646970; 1223 } 1224 1239 1225 .filikod-alt-audit-table .column-uploaded-to a { 1240 1226 white-space: nowrap; … … 1590 1576 } 1591 1577 1578 /* Images semblables : bouton « C'est voulu » dans l'en-tête de groupe, filtres par langue */ 1579 .filikod-alt-audit-table tr.filikod-dup-group-row .filikod-visual-ignore { 1580 float: right; 1581 margin: -2px 0; 1582 font-weight: 400; 1583 } 1584 1585 .filikod-visual-filters a.current { 1586 font-weight: 600; 1587 color: #1d2327; 1588 text-decoration: none; 1589 } 1590 1592 1591 .filikod-duplicates-scan-top { 1593 1592 margin: 0 0 20px 0; -
filikod/trunk/assets/js/admin.js
r3737001 r3763820 28 28 // Duplicates : scan par lots (empreinte md5 des fichiers) 29 29 initDuplicatesScan(); 30 31 // Duplicates > Similar images : « C'est voulu » / « Afficher de nouveau » 32 initVisualIgnore(); 30 33 }); 34 35 /** 36 * Images semblables : marque un groupe comme voulu (ou le rétablit), puis recharge la liste. 37 */ 38 function initVisualIgnore() { 39 $(document).on('click', '.filikod-visual-ignore', function(e) { 40 e.preventDefault(); 41 var $button = $(this); 42 $button.prop('disabled', true); 43 $.post(filikodAdmin.ajaxUrl, { 44 action: 'filikod_visual_ignore', 45 nonce: filikodAdmin.nonce, 46 key: String($button.data('key')), 47 ignore: String($button.data('ignore')) 48 }).done(function(response) { 49 if (response && response.success) { 50 window.location.reload(); 51 return; 52 } 53 $button.prop('disabled', false); 54 window.alert((response && response.data && response.data.message) || filikodAdmin.strings.errorOccurred); 55 }).fail(function() { 56 $button.prop('disabled', false); 57 window.alert(filikodAdmin.strings.errorOccurred); 58 }); 59 }); 60 } 31 61 32 62 /** -
filikod/trunk/filikod.php
r3737001 r3763820 4 4 * Plugin URI: https://filikod.com 5 5 * Description: Free alt text checker for WordPress: find missing, generic or duplicate image alt text, get an ALT Quality Score (0-100%) and fix it all in bulk. 6 * Version: 1. 2.16 * Version: 1.3.0 7 7 * Author: Filikod 8 8 * Author URI: https://filikod.com … … 22 22 23 23 // Définir les constantes du plugin 24 define('FILIKOD_VERSION', '1. 2.1');24 define('FILIKOD_VERSION', '1.3.0'); 25 25 define('FILIKOD_PLUGIN_URL', plugin_dir_url(__FILE__)); 26 26 define('FILIKOD_PLUGIN_PATH', plugin_dir_path(__FILE__)); … … 76 76 */ 77 77 public $duplicates; 78 79 /** 80 * Instance de la classe Visual Duplicates (images semblables) 81 */ 82 public $visual_duplicates; 78 83 79 84 /** … … 121 126 require_once FILIKOD_PLUGIN_PATH . 'includes/security/class-filikod-svg-security.php'; 122 127 require_once FILIKOD_PLUGIN_PATH . 'includes/duplicates/class-filikod-duplicates.php'; 128 require_once FILIKOD_PLUGIN_PATH . 'includes/duplicates/class-filikod-visual-duplicates.php'; 123 129 } 124 130 … … 128 134 public function init() { 129 135 // Traductions embarquées (languages/filikod-xx_XX.mo) ; le pack de langue de translate.wordpress.org, 130 // chargé automatiquement par WordPress, garde la priorité quand il existe. 136 // chargé automatiquement par WordPress, garde la priorité quand il existe. Sans cet appel, la traduction 137 // française embarquée ne serait pas chargée tant que le pack fr n'est pas validé sur translate.wordpress.org. 138 // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Bundled translations until the language packs are validated. 131 139 load_plugin_textdomain('filikod', false, dirname(FILIKOD_PLUGIN_BASENAME) . '/languages'); 132 140 … … 140 148 $this->svg_security = new Filikod_SVG_Security(); 141 149 $this->duplicates = new Filikod_Duplicates(); 150 $this->visual_duplicates = new Filikod_Visual_Duplicates(); 142 151 if ( class_exists( 'Filikod_Alt_Audit' ) ) { 143 152 Filikod_Alt_Audit::register_alt_meta_cache_hooks(); … … 180 189 'filikod_alt_audit_v5', 181 190 'filikod_alt_audit_v6', 191 'filikod_alt_audit_v7', 192 'filikod_alt_audit_v8', 182 193 'filikod_alt_norm_counts_v1', 183 194 'filikod_alt_norm_counts_v2', 184 195 'filikod_alt_norm_counts_v3', 196 'filikod_alt_norm_counts_v4', 185 197 ); 186 198 foreach ($legacy_transients as $legacy) { -
filikod/trunk/includes/accessibility/class-filikod-accessibility.php
r3737001 r3763820 23 23 24 24 /** 25 * Images reçues pendant la requête, repassées en fin de requête avec leurs copies multilingues 26 * (voir process_pending_attachments()). Clé « site:ID », valeur array(site, ID) : en multisite, 27 * une extension peut avoir changé de site entre l'envoi et la fin de requête. 28 * 29 * @var array<string, int[]> 30 */ 31 private static $pending_ids = array(); 32 33 /** 25 34 * Constructeur - Initialise la classe 26 35 */ … … 35 44 * Les hooks utilisés : 36 45 * - 'add_attachment' : Intercepte l'ajout d'une nouvelle image (après insertion dans la base) 46 * - 'shutdown' (posé à la demande) : repasse les images de la requête et leurs copies multilingues 37 47 * - 'wp_get_attachment_image_attributes' : Filtre les attributs des images dans le contenu 38 48 */ … … 54 64 */ 55 65 public function process_new_attachment($attachment_id) { 66 $attachment_id = (int) $attachment_id; 67 if (!self::has_upload_options()) { 68 return; 69 } 70 71 // Multilingue : WPML Media Translation crée une copie de l'image par langue (même fichier, 72 // même seconde, ID suivants). Ces copies ne passaient jamais ici avec leur fichier attaché : 73 // seule l'image envoyée recevait l'ALT, les copies des autres langues restaient vides 74 // (constaté avec WPML 5.0). Tout est repassé en fin de requête, quand les copies sont complètes. 75 if (empty(self::$pending_ids)) { 76 add_action('shutdown', array($this, 'process_pending_attachments')); 77 } 78 $blog_id = get_current_blog_id(); 79 self::$pending_ids[$blog_id . ':' . $attachment_id] = array($blog_id, $attachment_id); 80 56 81 // Vérifier que c'est une image 57 82 if (!wp_attachment_is_image($attachment_id)) { … … 59 84 } 60 85 86 $this->apply_upload_options($attachment_id); 87 } 88 89 /** 90 * Applique à une image les options prévues à l'envoi, dans l'ordre : 91 * ALT automatique, nettoyage des caractères spéciaux, suppression du titre. 92 * Chaque étape est sans effet si elle a déjà été faite : l'appeler deux fois ne change rien. 93 * 94 * @param int $attachment_id L'ID de l'attachment 95 */ 96 private function apply_upload_options($attachment_id) { 61 97 // 1) Générer le texte ALT si activé (priorité : titre de l’attachment puis nom de fichier) 62 98 if (get_option('filikod_auto_alt', 'no') === 'yes') { … … 71 107 $this->clear_attachment_title($attachment_id); 72 108 } 109 } 110 111 /** 112 * Au moins une option d'envoi est active (ALT automatique, nettoyage, suppression du titre). 113 * 114 * @return bool 115 */ 116 private static function has_upload_options() { 117 return get_option('filikod_auto_alt', 'no') === 'yes' 118 || get_option('filikod_clean_alt_special_chars', 'no') === 'yes' 119 || get_option('filikod_remove_title', 'no') === 'yes'; 120 } 121 122 /** 123 * Fin de requête : repasse les images reçues et les copies multilingues créées après elles. 124 * 125 * Une image déjà traitée à l'envoi ne bouge plus (chaque option est sans effet la deuxième fois) ; 126 * une copie WPML reçoit le même traitement qu'une image envoyée seule. Les traductions plus 127 * anciennes que l'image reçue ne sont jamais touchées : elles relèvent de « Process Existing Images ». 128 */ 129 public function process_pending_attachments() { 130 $pending = self::$pending_ids; 131 self::$pending_ids = array(); 132 133 foreach ($pending as $item) { 134 list($blog_id, $attachment_id) = $item; 135 $switched = is_multisite() && get_current_blog_id() !== $blog_id; 136 if ($switched) { 137 switch_to_blog($blog_id); 138 } 139 140 // Options relues ici : en multisite, chaque site a les siennes. 141 if (self::has_upload_options()) { 142 $ids = array_merge(array($attachment_id), self::get_later_translation_ids($attachment_id)); 143 foreach ($ids as $id) { 144 // Les copies ont pu recevoir leur fichier et leurs métas hors des fonctions de WordPress : 145 // relire la base plutôt qu'un cache objet (Redis, Memcached) amorcé avant. 146 wp_cache_delete($id, 'posts'); 147 wp_cache_delete($id, 'post_meta'); 148 if (get_post_type($id) !== 'attachment' || !wp_attachment_is_image($id)) { 149 continue; 150 } 151 $this->apply_upload_options($id); 152 } 153 } 154 155 if ($switched) { 156 restore_current_blog(); 157 } 158 } 159 } 160 161 /** 162 * IDs des traductions d'une image créées après elle (copies du même envoi), sans l'image elle-même. 163 * 164 * @param int $attachment_id L'ID de l'attachment 165 * @return int[] 166 */ 167 private static function get_later_translation_ids($attachment_id) { 168 if (!class_exists('Filikod_Duplicates')) { 169 return array(); 170 } 171 $engine = Filikod_Duplicates::get_multilingual_engine(); 172 if ($engine === '') { 173 return array(); 174 } 175 176 if ($engine === 'wpml') { 177 // Lecture directe de la table de WPML, sans ses caches, comme Filikod_Duplicates::get_translation_family_map(). 178 global $wpdb; 179 $table = esc_sql($wpdb->prefix . 'icl_translations'); 180 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- WPML core table, name derived from $wpdb->prefix; one query per image received in this request. 181 $ids = $wpdb->get_col( 182 $wpdb->prepare( 183 "SELECT t2.element_id 184 FROM {$table} t1 185 INNER JOIN {$table} t2 ON t2.trid = t1.trid AND t2.element_type = t1.element_type 186 WHERE t1.element_type = 'post_attachment' 187 AND t1.element_id = %d", 188 $attachment_id 189 ) 190 ); 191 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared 192 } else { 193 $ids = Filikod_Duplicates::get_translation_ids($attachment_id); 194 } 195 196 $later = array(); 197 foreach ((array) $ids as $id) { 198 $id = (int) $id; 199 if ($id > $attachment_id) { 200 $later[] = $id; 201 } 202 } 203 return $later; 73 204 } 74 205 … … 240 371 */ 241 372 public static function clean_special_chars_value($alt) { 242 $alt = (string) $alt; 373 $alt = (string) $alt; 374 375 // ALT multilingue WPGlobus / qTranslate-XT : on nettoie le texte de chaque langue, jamais les 376 // balises « {:fr} » et « [:fr] », dont les accolades et crochets font partie des caractères retirés. 377 if (is_callable(array('Filikod_Alt_Audit', 'map_multilingual_alt'))) { 378 $by_language = Filikod_Alt_Audit::map_multilingual_alt($alt, array(__CLASS__, 'clean_special_chars_value')); 379 if (null !== $by_language) { 380 return $by_language; 381 } 382 } 383 243 384 $trimmed = trim($alt); 244 385 -
filikod/trunk/includes/admin/class-filikod-admin.php
r3737001 r3763820 30 30 add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); 31 31 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); 32 add_action( 'admin_footer', array( $this, 'inject_menu_icon_script' ) );33 32 if ( class_exists( 'Filikod_Media_Alt_Status' ) ) { 34 33 new Filikod_Media_Alt_Status(); … … 40 39 */ 41 40 public function add_admin_menu() { 42 // Menu principal - Utiliser 'none' pour l'icône et l'injecter via CSS.41 // Menu principal. 43 42 // Le titre de menu est la marque, volontairement non traduite : WordPress en dérive 44 43 // le hook suffix des sous-pages (sanitize_title), et une translittération le casserait. … … 50 49 'filikod', // Slug du menu 51 50 array( $this->plugin->dashboard, 'display_dashboard_page' ), // Fonction callback 52 'none', // Pas d'icône par défaut, on l'ajoute via CSS51 $this->get_menu_icon_url(), // Icône SVG, recolorée par WordPress 53 52 30 // Position 54 53 ); … … 103 102 */ 104 103 public function enqueue_admin_assets( $hook ) { 105 // C harger le CSS sur toutes les pages admin pour que l'icône soit visible partout106 // C'est nécessaire car le menu est visible sur toutes les pages admin104 // CSS sur toutes les pages admin : les badges ALT de la médiathèque s'affichent aussi 105 // dans la fenêtre « Ajouter un média », ouverte depuis n'importe quel écran. 107 106 wp_enqueue_style( 108 107 'filikod-admin', … … 174 173 } 175 174 wp_localize_script( 'filikod-admin', 'filikodAdmin', $localize ); 176 177 // Ajouter le script inline pour injecter l'icône SVG du menu (fallback pour les pages du plugin) 178 $svg = $this->get_svg_icon(); 179 $inline_script = sprintf( 180 "(function($) { 181 $(document).ready(function() { 182 var menuIcon = $('#toplevel_page_filikod .wp-menu-image'); 183 if (menuIcon.length && !menuIcon.find('svg').length) { 184 var svgIcon = %s; 185 menuIcon.html(svgIcon); 186 } 187 }); 188 })(jQuery);", 189 wp_json_encode( $svg, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) 190 ); 191 wp_add_inline_script( 'filikod-admin', $inline_script ); 192 } 193 194 /** 195 * Injecter l'icône SVG du menu via JavaScript 196 * Utilise admin_footer pour s'exécuter sur toutes les pages admin 197 * Conforme aux bonnes pratiques WordPress 198 */ 199 public function inject_menu_icon_script() { 200 // Vérifier que le menu existe avant d'injecter le script 201 global $menu; 202 $menu_exists = false; 203 if ( is_array( $menu ) ) { 204 foreach ( $menu as $menu_item ) { 205 if ( isset( $menu_item[2] ) && 'filikod' === $menu_item[2] ) { 206 $menu_exists = true; 207 break; 208 } 209 } 210 } 211 212 if ( ! $menu_exists ) { 213 return; 214 } 215 216 $svg = $this->get_svg_icon(); 217 wp_register_script( 'filikod-admin-menu-icon', false, array(), FILIKOD_VERSION, false ); 218 wp_enqueue_script( 'filikod-admin-menu-icon' ); 219 wp_localize_script( 'filikod-admin-menu-icon', 'filikodMenuIconSvg', array( 'svg' => $svg ) ); 220 $inline_js = "(function() {\n" 221 . " function injectFilikodIcon() {\n" 222 . " var menuIcon = document.querySelector('#toplevel_page_filikod .wp-menu-image');\n" 223 . " if (menuIcon && !menuIcon.querySelector('svg')) {\n" 224 . " menuIcon.innerHTML = filikodMenuIconSvg.svg;\n" 225 . " }\n" 226 . " }\n" 227 . " if (document.readyState === 'loading') {\n" 228 . " document.addEventListener('DOMContentLoaded', injectFilikodIcon);\n" 229 . " } else {\n" 230 . " injectFilikodIcon();\n" 231 . " }\n" 232 . " setTimeout(injectFilikodIcon, 100);\n" 233 . "})();"; 234 wp_add_inline_script( 'filikod-admin-menu-icon', $inline_js, 'after' ); 235 } 236 237 /** 238 * Obtenir le SVG de l'icône 175 } 176 177 /** 178 * Icône du menu en SVG encodé (data URI), le format prévu par add_menu_page(). 239 179 * 240 * @return string SVG avec currentColor 180 * WordPress l'affiche dès le rendu de la page, sans JavaScript, et son script svg-painter 181 * remplace chaque fill="…" par les couleurs du jeu de couleurs de l'utilisateur (normal, 182 * survol, page active), comme pour les icônes du cœur. L'ancienne icône, injectée en 183 * JavaScript après le chargement, apparaissait en retard ou pas du tout, dans un gris codé 184 * en dur qui ne suivait ni le jeu de couleurs ni le survol. 185 * 186 * @return string 187 */ 188 private function get_menu_icon_url() { 189 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Menu icon data URI: svg-painter only recolors base64 SVGs. 190 return 'data:image/svg+xml;base64,' . base64_encode( $this->get_svg_icon() ); 191 } 192 193 /** 194 * SVG de l'icône du menu. Le fill #a7aaad (couleur des icônes du jeu par défaut) sert 195 * seulement le temps que svg-painter applique les couleurs de l'utilisateur. 196 * 197 * @return string 241 198 */ 242 199 private function get_svg_icon() { 243 return '<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 20.0468L14 20.0468L4.50001 6.04681L1 11.5468V20.0468Z" fill="currentColor"/><path d="M7.00001 20.0468H19L13 11.0468L7.00001 20.0468Z" fill="currentColor"/><path d="M16 4.04688L14.5 2.04688L19.8595 2.14577e-05L17.5 2.04688L19 3.98544L12.9356 8.4258L16 4.04688Z" fill="currentColor"/><path d="M2.00001 6.04681V16.0468C2.00001 17.0179 2.00228 17.6458 2.06446 18.1083C2.12278 18.5419 2.21686 18.6777 2.29297 18.7538C2.36908 18.8299 2.50494 18.924 2.93848 18.9824C3.40099 19.0445 4.02893 19.0468 5.00001 19.0468H15C15.9711 19.0468 16.599 19.0445 17.0615 18.9824C17.4951 18.924 17.6309 18.8299 17.707 18.7538C17.7831 18.6777 17.8772 18.5419 17.9356 18.1083C17.9977 17.6458 18 17.0179 18 16.0468V10.0468H20V16.0468C20 16.9613 20.0022 17.7482 19.918 18.3749C19.8298 19.0303 19.6307 19.6583 19.1211 20.1679C18.6115 20.6775 17.9835 20.8766 17.3281 20.9648C16.7014 21.049 15.9145 21.0468 15 21.0468H5.00001C4.08547 21.0468 3.29863 21.049 2.67188 20.9648C2.01648 20.8766 1.38849 20.6775 0.878912 20.1679C0.369331 19.6583 0.170191 19.0303 0.0820374 18.3749C-0.00222694 17.7482 5.65403e-06 16.9613 5.65403e-06 16.0468V6.04681C5.65403e-06 5.13227 -0.00222695 4.34543 0.0820374 3.71868C0.170191 3.06328 0.369331 2.43529 0.878912 1.92571C1.38849 1.41613 2.01648 1.21699 2.67188 1.12884C3.29863 1.04457 4.08547 1.04681 5.00001 1.04681H10V3.04681H5.00001C4.02893 3.04681 3.40099 3.04908 2.93848 3.11126C2.50494 3.16958 2.36908 3.26366 2.29297 3.33977C2.21686 3.41589 2.12278 3.55174 2.06446 3.98528C2.00228 4.44779 2.00001 5.07573 2.00001 6.04681Z" fill="currentColor"/></svg>';200 return '<svg width="20" height="22" viewBox="0 0 20 22" xmlns="http://www.w3.org/2000/svg"><path d="M1 20.0468L14 20.0468L4.50001 6.04681L1 11.5468V20.0468Z" fill="#a7aaad"/><path d="M7.00001 20.0468H19L13 11.0468L7.00001 20.0468Z" fill="#a7aaad"/><path d="M16 4.04688L14.5 2.04688L19.8595 2.14577e-05L17.5 2.04688L19 3.98544L12.9356 8.4258L16 4.04688Z" fill="#a7aaad"/><path d="M2.00001 6.04681V16.0468C2.00001 17.0179 2.00228 17.6458 2.06446 18.1083C2.12278 18.5419 2.21686 18.6777 2.29297 18.7538C2.36908 18.8299 2.50494 18.924 2.93848 18.9824C3.40099 19.0445 4.02893 19.0468 5.00001 19.0468H15C15.9711 19.0468 16.599 19.0445 17.0615 18.9824C17.4951 18.924 17.6309 18.8299 17.707 18.7538C17.7831 18.6777 17.8772 18.5419 17.9356 18.1083C17.9977 17.6458 18 17.0179 18 16.0468V10.0468H20V16.0468C20 16.9613 20.0022 17.7482 19.918 18.3749C19.8298 19.0303 19.6307 19.6583 19.1211 20.1679C18.6115 20.6775 17.9835 20.8766 17.3281 20.9648C16.7014 21.049 15.9145 21.0468 15 21.0468H5.00001C4.08547 21.0468 3.29863 21.049 2.67188 20.9648C2.01648 20.8766 1.38849 20.6775 0.878912 20.1679C0.369331 19.6583 0.170191 19.0303 0.0820374 18.3749C-0.00222694 17.7482 5.65403e-06 16.9613 5.65403e-06 16.0468V6.04681C5.65403e-06 5.13227 -0.00222695 4.34543 0.0820374 3.71868C0.170191 3.06328 0.369331 2.43529 0.878912 1.92571C1.38849 1.41613 2.01648 1.21699 2.67188 1.12884C3.29863 1.04457 4.08547 1.04681 5.00001 1.04681H10V3.04681H5.00001C4.02893 3.04681 3.40099 3.04908 2.93848 3.11126C2.50494 3.16958 2.36908 3.26366 2.29297 3.33977C2.21686 3.41589 2.12278 3.55174 2.06446 3.98528C2.00228 4.44779 2.00001 5.07573 2.00001 6.04681Z" fill="#a7aaad"/></svg>'; 244 201 } 245 202 -
filikod/trunk/includes/admin/class-filikod-media-alt-status.php
r3737001 r3763820 148 148 $image_alt = wp_unslash( $attachment['image_alt'] ); 149 149 if ( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) !== $image_alt ) { 150 $image_alt = wp_strip_all_tags( $image_alt, true ); 150 // Ancienne forme qTranslate « <!--:fr--> » convertie en « [:fr] » avant de retirer le HTML. 151 $image_alt = wp_strip_all_tags( class_exists( 'Filikod_Alt_Audit' ) ? Filikod_Alt_Audit::protect_multilingual_markup( $image_alt ) : $image_alt, true ); 151 152 update_post_meta( $attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) ); 152 153 } -
filikod/trunk/includes/class-filikod-alt-audit.php
r3737001 r3763820 42 42 * reparcourir tous les fichiers à chaque requête (ex. preview modale). 43 43 */ 44 const ALT_NORM_COUNTS_TRANSIENT = 'filikod_alt_norm_counts_v 3';44 const ALT_NORM_COUNTS_TRANSIENT = 'filikod_alt_norm_counts_v5'; 45 45 46 46 /** 47 47 * Clé du transient d’audit (incrémenter à chaque évolution de structure). 48 48 */ 49 const AUDIT_TRANSIENT = 'filikod_alt_audit_v 7';49 const AUDIT_TRANSIENT = 'filikod_alt_audit_v9'; 50 50 51 51 /** … … 183 183 184 184 /** 185 * Balises de langue que WPGlobus et qTranslate-XT écrivent dans un seul champ ALT : 186 * « {:fr}Usine{:}{:en}Factory{:} », « [:fr]Usine[:en]Factory[:] » ou l'ancienne forme 187 * « <!--:fr-->Usine<!--:--> ». Ouverture avec code de langue (fr, en, pt-br) ou fermeture vide. 188 */ 189 const MULTILINGUAL_TAG_PATTERN = '/(\{:[a-z]{2}(?:[-_][a-z0-9]{2,4})?\}|\[:[a-z]{2}(?:[-_][a-z0-9]{2,4})?\]|<!--:[a-z]{2}(?:[-_][a-z0-9]{2,4})?-->|\{:\}|\[:\]|<!--:-->)/i'; 190 191 /** 192 * Découpe un ALT multilingue (WPGlobus, qTranslate-XT) en textes par langue. 193 * 194 * @param string $alt ALT brut. 195 * @return array<string, string>|null Code de langue (2 lettres) => texte, null si l'ALT n'a pas de balise de langue. 196 */ 197 public static function split_multilingual_alt( $alt ) { 198 if ( ! is_string( $alt ) || ( false === strpos( $alt, '{:' ) && false === strpos( $alt, '[:' ) && false === strpos( $alt, '<!--:' ) ) ) { 199 return null; 200 } 201 $parts = preg_split( self::MULTILINGUAL_TAG_PATTERN, $alt, -1, PREG_SPLIT_DELIM_CAPTURE ); 202 $segments = array(); 203 $lang = null; 204 foreach ( $parts as $i => $part ) { 205 if ( 1 === $i % 2 ) { 206 // Délimiteur : ouverture d'une langue, ou fermeture ({:}, [:], <!--:-->). 207 $lang = preg_match( '/[a-z]{2}/i', substr( $part, 2 ), $m ) ? strtolower( $m[0] ) : null; 208 if ( null !== $lang && ! isset( $segments[ $lang ] ) ) { 209 $segments[ $lang ] = ''; 210 } 211 continue; 212 } 213 if ( null !== $lang ) { 214 $segments[ $lang ] .= $part; 215 } 216 } 217 if ( empty( $segments ) ) { 218 return null; 219 } 220 return array_map( 'trim', $segments ); 221 } 222 223 /** 224 * Applique une fonction au texte de chaque langue d'un ALT multilingue en gardant ses balises 225 * (nettoyage des caractères spéciaux : les accolades et crochets des balises ne doivent pas partir). 226 * 227 * @param string $alt ALT brut. 228 * @param callable $callback Reçoit et renvoie le texte d'une langue. 229 * @return string|null ALT reconstruit, null si l'ALT n'a pas de balise de langue. 230 */ 231 public static function map_multilingual_alt( $alt, $callback ) { 232 if ( null === self::split_multilingual_alt( $alt ) ) { 233 return null; 234 } 235 $parts = preg_split( self::MULTILINGUAL_TAG_PATTERN, $alt, -1, PREG_SPLIT_DELIM_CAPTURE ); 236 foreach ( $parts as $i => $part ) { 237 if ( 0 === $i % 2 && trim( $part ) !== '' ) { 238 $parts[ $i ] = call_user_func( $callback, $part ); 239 } 240 } 241 return implode( '', $parts ); 242 } 243 244 /** 245 * Remplace l'ancienne forme qTranslate « <!--:fr--> » par « [:fr] » avant un nettoyage HTML 246 * (sanitize_text_field, wp_strip_all_tags) qui supprimerait ces commentaires et mélangerait les langues. 247 * 248 * @param string $alt ALT brut. 249 * @return string 250 */ 251 public static function protect_multilingual_markup( $alt ) { 252 if ( ! is_string( $alt ) || false === strpos( $alt, '<!--:' ) ) { 253 return (string) $alt; 254 } 255 return preg_replace( '/<!--:([a-z]{2}(?:[-_][a-z0-9]{2,4})?)?-->/i', '[:$1]', $alt ); 256 } 257 258 /** 259 * Extension qui écrit plusieurs langues dans le même champ ALT. 260 * 261 * @return string 'wpglobus' | 'qtranslate' | ''. 262 */ 263 public static function get_alt_language_engine() { 264 if ( defined( 'WPGLOBUS_VERSION' ) || class_exists( 'WPGlobus', false ) ) { 265 return 'wpglobus'; 266 } 267 if ( defined( 'QTX_VERSION' ) || ! empty( $GLOBALS['q_config']['enabled_languages'] ) ) { 268 return 'qtranslate'; 269 } 270 return ''; 271 } 272 273 /** 274 * Langues activées dans WPGlobus ou qTranslate-XT, langue par défaut en premier (codes à 2 lettres). 275 * Chaque ALT multilingue doit avoir un texte dans chacune ; vide quand aucune des deux n'est active. 276 * 277 * @return string[] 278 */ 279 public static function get_alt_languages() { 280 $languages = array(); 281 $default = ''; 282 if ( 'wpglobus' === self::get_alt_language_engine() && is_callable( array( 'WPGlobus', 'Config' ) ) ) { 283 $config = call_user_func( array( 'WPGlobus', 'Config' ) ); 284 if ( is_object( $config ) ) { 285 $languages = isset( $config->enabled_languages ) ? (array) $config->enabled_languages : array(); 286 $default = isset( $config->default_language ) ? (string) $config->default_language : ''; 287 } 288 } elseif ( 'qtranslate' === self::get_alt_language_engine() ) { 289 $q_config = isset( $GLOBALS['q_config'] ) && is_array( $GLOBALS['q_config'] ) ? $GLOBALS['q_config'] : array(); 290 $languages = isset( $q_config['enabled_languages'] ) ? (array) $q_config['enabled_languages'] : array(); 291 $default = isset( $q_config['default_language'] ) ? (string) $q_config['default_language'] : ''; 292 } 293 $out = array(); 294 foreach ( array_merge( array( $default ), $languages ) as $code ) { 295 $code = strtolower( substr( (string) $code, 0, 2 ) ); 296 if ( preg_match( '/^[a-z]{2}$/', $code ) && ! in_array( $code, $out, true ) ) { 297 $out[] = $code; 298 } 299 } 300 return $out; 301 } 302 303 /** 304 * Clés de l'ALT pour la détection des doublons : l'ALT normalisé, ou un « langue:texte » par langue 305 * pour un ALT multilingue (deux images qui partagent leur texte français sont en doublon en français). 306 * Les deux-points ne survivent pas à normalize_alt() : ces clés ne croisent jamais un ALT simple. 307 * 308 * @param string $alt ALT brut. 309 * @return string[] 310 */ 311 public static function alt_keys( $alt ) { 312 $segments = self::split_multilingual_alt( $alt ); 313 if ( null === $segments ) { 314 $norm = self::normalize_alt( $alt ); 315 return $norm === '' ? array() : array( $norm ); 316 } 317 $keys = array(); 318 foreach ( $segments as $lang => $text ) { 319 $norm = self::normalize_alt( $text ); 320 if ( $norm !== '' ) { 321 $keys[] = $lang . ':' . $norm; 322 } 323 } 324 return array_values( array_unique( $keys ) ); 325 } 326 327 /** 185 328 * Récupère la langue et la locale WordPress. 186 329 * … … 451 594 continue; 452 595 } 453 $norm = self::normalize_alt( $alt ); 454 if ( $norm !== '' ) { 455 self::add_identity( $identities, $norm, self::identity_for( (int) $id ) ); 596 foreach ( self::alt_keys( $alt ) as $key ) { 597 self::add_identity( $identities, $key, self::identity_for( (int) $id ) ); 456 598 } 457 599 } … … 482 624 continue; 483 625 } 484 $norm = self::normalize_alt( $alt ); 485 if ( $norm !== '' ) { 486 self::add_identity( $identities, $norm, self::identity_for( $id ) ); 626 foreach ( self::alt_keys( $alt ) as $key ) { 627 self::add_identity( $identities, $key, self::identity_for( $id ) ); 487 628 } 488 629 } … … 521 662 continue; 522 663 } 523 $ norm = self::normalize_alt( $raw );524 if ( $norm === '') {664 $keys = self::alt_keys( $raw ); 665 if ( empty( $keys ) ) { 525 666 continue; 526 667 } … … 528 669 ? Filikod_Duplicates::identity_from_hash( (int) $row->ID, isset( $row->hash ) ? $row->hash : null ) 529 670 : 'id:' . (int) $row->ID; 530 self::add_identity( $map, $norm, $identity ); 671 foreach ( $keys as $key ) { 672 self::add_identity( $map, $key, $identity ); 673 } 531 674 } 532 675 return $map; … … 572 715 $old_raw = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); 573 716 $old_raw = is_string( $old_raw ) ? $old_raw : ''; 574 $old_norm = self::normalize_alt( $old_raw ); 575 $new_norm = self::normalize_alt( $alt_raw ); 576 if ( $old_norm !== '' ) { 577 self::remove_identity( $adj, $old_norm, $identity ); 578 } 579 if ( $new_norm !== '' ) { 580 self::add_identity( $adj, $new_norm, $identity ); 717 foreach ( self::alt_keys( $old_raw ) as $key ) { 718 self::remove_identity( $adj, $key, $identity ); 719 } 720 foreach ( self::alt_keys( $alt_raw ) as $key ) { 721 self::add_identity( $adj, $key, $identity ); 581 722 } 582 723 return self::counts_from_identity_map( $adj ); … … 599 740 return array( 'decorative' ); 600 741 } 601 $ dup_map = self::duplicate_map_for_alt_preview( $attachment_id, is_string( $alt_raw ) ? $alt_raw : '' );602 $ generic_terms = self::get_audit_generic_terms();603 $ alt_norm = self::normalize_alt( is_string( $alt_raw ) ? $alt_raw : '');604 return self::collect_alt_issue_codes( $alt_norm, $generic_terms, $dup_map, false );742 $alt_raw = is_string( $alt_raw ) ? $alt_raw : ''; 743 $dup_map = self::duplicate_map_for_alt_preview( $attachment_id, $alt_raw ); 744 $result = self::evaluate_alt( $alt_raw, $dup_map ); 745 return $result['issues']; 605 746 } 606 747 … … 675 816 $alt_raw = is_string( $alt_raw ) ? maybe_unserialize( $alt_raw ) : ''; 676 817 $alt_raw = is_string( $alt_raw ) ? $alt_raw : ''; 677 $alt_norm = self::normalize_alt( $alt_raw ); 678 679 // 1. missing 818 819 $result = self::evaluate_alt( $alt_raw, $dup_map ); 820 $result['alt_raw'] = $alt_raw; 821 return $result; 822 } 823 824 /** 825 * Évalue un ALT brut : simple, ou multilingue (WPGlobus, qTranslate-XT) langue par langue. 826 * 827 * Un ALT multilingue est jugé sur sa langue la plus faible : chaque langue attendue doit avoir un 828 * texte (sinon « missing »), puis chaque texte passe les règles habituelles, avec les termes 829 * génériques de sa langue. La longueur se mesure par langue : un ALT bilingue n'est plus « too_long » 830 * parce que ses deux langues sont comptées ensemble. 831 * 832 * @param string $alt_raw ALT brut. 833 * @param array<string, int> $dup_map Map clé d'ALT (alt_keys()) => images distinctes. 834 * @return array{score: int, status: string, alt_normalized: string, issues: string[], languages: array|null} 835 */ 836 public static function evaluate_alt( $alt_raw, $dup_map ) { 837 $segments = self::split_multilingual_alt( $alt_raw ); 838 if ( null === $segments ) { 839 $alt_norm = self::normalize_alt( $alt_raw ); 840 $result = self::evaluate_plain_alt( $alt_norm, self::get_audit_generic_terms(), $dup_map, $alt_norm ); 841 $result['languages'] = null; 842 return $result; 843 } 844 845 $expected = self::get_alt_languages(); 846 if ( empty( $expected ) ) { 847 // Balises sans WPGlobus ni qTranslate-XT actif : on contrôle les langues présentes. 848 $expected = array_keys( $segments ); 849 } 850 $present = array(); 851 $missing = array(); 852 $worst = null; 853 $issues = array(); 854 $norms = array(); 855 foreach ( $expected as $lang ) { 856 $norm = self::normalize_alt( isset( $segments[ $lang ] ) ? $segments[ $lang ] : '' ); 857 if ( $norm === '' ) { 858 $missing[] = $lang; 859 continue; 860 } 861 $present[] = $lang; 862 $norms[] = $norm; 863 $result = self::evaluate_plain_alt( $norm, self::get_generic_terms_for_language( $lang ), $dup_map, $lang . ':' . $norm ); 864 $issues = array_merge( $issues, $result['issues'] ); 865 if ( null === $worst || $result['score'] < $worst['score'] ) { 866 $worst = $result; 867 } 868 } 869 870 if ( ! empty( $missing ) || null === $worst ) { 871 $worst = array( 872 'score' => 0, 873 'status' => 'missing', 874 ); 875 array_unshift( $issues, 'missing' ); 876 } 877 return array( 878 'score' => $worst['score'], 879 'status' => $worst['status'], 880 'alt_normalized' => implode( ' ', $norms ), 881 'issues' => array_values( array_unique( $issues ) ), 882 'languages' => array( 883 'present' => $present, 884 'missing' => $missing, 885 ), 886 ); 887 } 888 889 /** 890 * Score et statut d'un texte ALT normalisé (une seule langue). 891 * Ordre : missing → generic → too_short → duplicate → too_long → correct. 892 * 893 * @param string $alt_norm ALT normalisé. 894 * @param string[] $generic_terms Termes génériques. 895 * @param array<string, int> $dup_map Map clé d'ALT => images distinctes. 896 * @param string $dup_key Clé de cet ALT dans $dup_map. 897 * @return array{score: int, status: string, alt_normalized: string, issues: string[]} 898 */ 899 private static function evaluate_plain_alt( $alt_norm, $generic_terms, $dup_map, $dup_key ) { 900 $issues = self::collect_alt_issue_codes( $alt_norm, $generic_terms, $dup_map, false, $dup_key ); 680 901 if ( $alt_norm === '' ) { 681 return array( 682 'score' => 0, 683 'status' => 'missing', 684 'alt_raw' => $alt_raw, 685 'alt_normalized' => $alt_norm, 686 ); 687 } 688 689 $generic_terms = self::get_audit_generic_terms(); 690 691 // 2. generic 692 if ( self::is_generic_alt( $alt_norm, $generic_terms ) ) { 693 return array( 694 'score' => 20, 695 'status' => 'generic', 696 'alt_raw' => $alt_raw, 697 'alt_normalized' => $alt_norm, 698 ); 699 } 700 701 // 3. too short (moins de 8 caractères) 702 if ( self::alt_length( $alt_norm ) < self::TOO_SHORT_MAX_LEN ) { 703 return array( 704 'score' => 40, 705 'status' => 'too_short', 706 'alt_raw' => $alt_raw, 707 'alt_normalized' => $alt_norm, 708 ); 709 } 710 711 // 4. duplicate 712 $count = isset( $dup_map[ $alt_norm ] ) ? (int) $dup_map[ $alt_norm ] : 0; 713 if ( $count >= 2 ) { 714 return array( 715 'score' => 60, 716 'status' => 'duplicate', 717 'alt_raw' => $alt_raw, 718 'alt_normalized' => $alt_norm, 719 ); 720 } 721 722 // 5. too long (> 125 caractères) : moins grave que doublon mais signalé pour a11y/SEO. 723 if ( self::alt_length( $alt_norm ) > self::TOO_LONG_MIN_LEN ) { 724 return array( 725 'score' => 70, 726 'status' => 'too_long', 727 'alt_raw' => $alt_raw, 728 'alt_normalized' => $alt_norm, 729 ); 730 } 731 732 // 6. correct → base 90 + bonus 733 $score = 90; 734 if ( self::alt_length( $alt_norm ) >= 8 ) { 735 $score += 5; 736 } 737 $word_count = count( preg_split( '/\s+/', $alt_norm, -1, PREG_SPLIT_NO_EMPTY ) ); 738 if ( $word_count >= 2 ) { 739 $score += 5; 740 } 741 $score = min( 100, $score ); 742 902 $score = 0; 903 $status = 'missing'; 904 } elseif ( in_array( 'generic', $issues, true ) ) { 905 $score = 20; 906 $status = 'generic'; 907 } elseif ( in_array( 'too_short', $issues, true ) ) { 908 $score = 40; 909 $status = 'too_short'; 910 } elseif ( in_array( 'duplicate', $issues, true ) ) { 911 $score = 60; 912 $status = 'duplicate'; 913 } elseif ( in_array( 'too_long', $issues, true ) ) { 914 // Moins grave que doublon mais signalé pour a11y/SEO. 915 $score = 70; 916 $status = 'too_long'; 917 } else { 918 // Correct → base 90 + bonus (8 caractères et plus, deux mots et plus). 919 $score = 95; 920 $status = 'correct'; 921 if ( count( preg_split( '/\s+/', $alt_norm, -1, PREG_SPLIT_NO_EMPTY ) ) >= 2 ) { 922 $score += 5; 923 } 924 } 743 925 return array( 744 'score' => $score,745 'status' => 'correct',746 'alt_ raw' => $alt_raw,747 ' alt_normalized' => $alt_norm,926 'score' => $score, 927 'status' => $status, 928 'alt_normalized' => $alt_norm, 929 'issues' => $issues, 748 930 ); 931 } 932 933 /** 934 * Termes génériques pour le texte d'une langue donnée : ceux de l'audit (anglais + langue du site) 935 * plus ceux de cette langue (« Bild » dans l'ALT allemand d'un site français). 936 * 937 * @param string $lang Code à 2 lettres. 938 * @return string[] 939 */ 940 private static function get_generic_terms_for_language( $lang ) { 941 static $cache = array(); 942 if ( ! isset( $cache[ $lang ] ) ) { 943 $terms = self::get_audit_generic_terms(); 944 if ( isset( self::$generic_terms_by_lang[ $lang ] ) ) { 945 $terms = array_values( array_unique( array_merge( $terms, self::get_generic_terms( $lang, $lang ) ) ) ); 946 } 947 $cache[ $lang ] = $terms; 948 } 949 return $cache[ $lang ]; 749 950 } 750 951 … … 757 958 * @param array<string, int> $dup_map Map alt normalisé => occurrences. 758 959 * @param bool $is_decorative Image marquée décorative. 960 * @param string|null $dup_key Clé dans $dup_map (« fr:usine » pour une langue d'un ALT multilingue) ; null = $alt_norm. 759 961 * @return string[] Codes : decorative, missing, generic, too_short, duplicate, too_long. 760 962 */ 761 public static function collect_alt_issue_codes( $alt_norm, $generic_terms, $dup_map, $is_decorative = false ) {963 public static function collect_alt_issue_codes( $alt_norm, $generic_terms, $dup_map, $is_decorative = false, $dup_key = null ) { 762 964 if ( $is_decorative ) { 763 965 return array( 'decorative' ); … … 774 976 $issues[] = 'too_short'; 775 977 } 776 $cnt = isset( $dup_map[ $alt_norm ] ) ? (int) $dup_map[ $alt_norm ] : 0; 978 $dup_key = null === $dup_key ? $alt_norm : (string) $dup_key; 979 $cnt = isset( $dup_map[ $dup_key ] ) ? (int) $dup_map[ $dup_key ] : 0; 777 980 if ( $cnt >= 2 ) { 778 981 $issues[] = 'duplicate'; … … 799 1002 foreach ( array_chunk( $ids, 1000 ) as $chunk ) { 800 1003 $placeholders = implode( ',', array_fill( 0, count( $chunk ), '%d' ) ); 801 // phpcs: ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholders %d générés pour la clause IN; résultat mis en cache dans le transient d'audit.1004 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- un %d généré par ID pour la clause IN, valeurs passées à prepare() ; résultat mis en cache dans le transient d'audit. 802 1005 $rows = $wpdb->get_results( 803 1006 $wpdb->prepare( … … 806 1009 ) 807 1010 ); 1011 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber 808 1012 if ( ! is_array( $rows ) ) { 809 1013 continue; … … 880 1084 continue; 881 1085 } 882 $result['issues'] = self::collect_alt_issue_codes( 883 isset( $result['alt_normalized'] ) ? $result['alt_normalized'] : '', 884 $generic_terms, 885 $dup_map, 886 false 887 ); 1086 if ( ! isset( $result['issues'] ) ) { 1087 $result['issues'] = self::collect_alt_issue_codes( 1088 isset( $result['alt_normalized'] ) ? $result['alt_normalized'] : '', 1089 $generic_terms, 1090 $dup_map, 1091 false 1092 ); 1093 } 888 1094 $items[ $id ] = $result; 889 1095 if ( isset( $counts[ $result['status'] ] ) ) { … … 912 1118 */ 913 1119 public static function get_image_attachment_ids( $limit = 0 ) { 914 $args = array( 915 'post_type' => 'attachment', 916 'post_status' => 'inherit', 917 'post_mime_type' => 'image', 918 'fields' => 'ids', 919 'posts_per_page' => $limit ? $limit : -1, 920 'orderby' => 'ID', 921 'order' => 'ASC', 922 // Multilingue : WPML et Polylang filtrent les requêtes admin sur la langue courante. 923 // L'audit doit couvrir toute la médiathèque, comme les compteurs SQL du plugin. 924 'suppress_filters' => true, 925 ); 926 $q = new WP_Query( $args ); 927 return is_array( $q->posts ) ? $q->posts : array(); 1120 global $wpdb; 1121 $limit = max( 0, (int) $limit ); 1122 // Requête directe plutôt que WP_Query : WPML et Polylang filtrent les requêtes admin sur la langue 1123 // courante, et l'audit doit couvrir toute la médiathèque, comme les compteurs SQL du plugin 1124 // (l'ancien 'suppress_filters' => true est refusé par Plugin Check). 1125 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Résultat mis en cache dans le transient d'audit. 1126 if ( $limit > 0 ) { 1127 $ids = $wpdb->get_col( 1128 $wpdb->prepare( 1129 "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type LIKE %s ORDER BY ID ASC LIMIT %d", 1130 'image/%', 1131 $limit 1132 ) 1133 ); 1134 } else { 1135 $ids = $wpdb->get_col( 1136 $wpdb->prepare( 1137 "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type LIKE %s ORDER BY ID ASC", 1138 'image/%' 1139 ) 1140 ); 1141 } 1142 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 1143 return is_array( $ids ) ? array_map( 'intval', $ids ) : array(); 928 1144 } 929 1145 … … 997 1213 return array( 'decorative' ); 998 1214 } 999 $dup_map = self::get_cached_global_alt_norm_counts(); 1000 $generic_terms = self::get_audit_generic_terms(); 1001 $result = self::score_for_attachment( $attachment_id, $dup_map ); 1002 $alt_norm = isset( $result['alt_normalized'] ) ? $result['alt_normalized'] : ''; 1003 return self::collect_alt_issue_codes( $alt_norm, $generic_terms, $dup_map, false ); 1215 $dup_map = self::get_cached_global_alt_norm_counts(); 1216 $result = self::score_for_attachment( $attachment_id, $dup_map ); 1217 return isset( $result['issues'] ) ? $result['issues'] : array(); 1004 1218 } 1005 1219 -
filikod/trunk/includes/dashboard/class-filikod-dashboard.php
r3737001 r3763820 168 168 wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'filikod' ) ) ); 169 169 } 170 $new_alt = isset( $_POST['filikod_alt_value'] ) ? sanitize_text_field( wp_unslash( $_POST['filikod_alt_value'] ) ) : ''; 170 $new_alt = isset( $_POST['filikod_alt_value'] ) ? wp_unslash( $_POST['filikod_alt_value'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized on the next line, after the multilingual markup is protected. 171 // qTranslate (ancienne forme « <!--:fr--> ») : sanitize_text_field supprimerait les balises et mélangerait les langues. 172 $new_alt = sanitize_text_field( class_exists( 'Filikod_Alt_Audit' ) ? Filikod_Alt_Audit::protect_multilingual_markup( (string) $new_alt ) : (string) $new_alt ); 171 173 $new_alt = trim( $new_alt ); 172 174 // Plafond de sécurité : un ALT légitime dépasse rarement 250 caractères ; on accepte 1000 pour rester souple. -
filikod/trunk/includes/duplicates/class-filikod-duplicates.php
r3704678 r3763820 50 50 */ 51 51 const OPTION_UPLOAD_ALERT = 'filikod_duplicate_upload_alert'; 52 53 /** 54 * Attachment ID => identity for the ALT audit, built once per request (see get_identity_map()). 55 * 56 * @var array<int, string>|null 57 */ 58 private static $identity_map = null; 52 59 53 60 /** … … 96 103 /** 97 104 * Index attachment ID => group (from the cached groups), built once per request. 105 * A group keeps one attachment per translation set (WPML, Polylang, Bogo): the other 106 * language versions of that attachment are indexed too, so each of them carries the 107 * badge in the Media Library, whatever the language it is browsed in. 98 108 * 99 109 * @return array<int, array{hash:string, ids:int[], sizes:array<int,int>}> … … 107 117 $index = array(); 108 118 $built_from = $stamp; 119 $families = empty( $data['groups'] ) ? array() : self::get_translation_family_map(); 120 $members = array(); 121 foreach ( $families as $member_id => $family ) { 122 $members[ $family ][] = (int) $member_id; 123 } 109 124 foreach ( $data['groups'] as $group ) { 110 125 foreach ( $group['ids'] as $id ) { 111 126 $index[ (int) $id ] = $group; 127 $family = isset( $families[ (int) $id ] ) ? $families[ (int) $id ] : ''; 128 if ( '' === $family ) { 129 continue; 130 } 131 foreach ( $members[ $family ] as $member_id ) { 132 if ( ! isset( $index[ $member_id ] ) ) { 133 $index[ $member_id ] = $group; 134 } 135 } 112 136 } 113 137 } … … 139 163 } 140 164 $reference_id = (int) $ids[0]; 165 // A language version of the reference is the reference too, not a copy of it. 166 $family = self::get_translation_family_of( $attachment_id ); 167 $is_reference = $reference_id === $attachment_id 168 || ( '' !== $family && self::get_translation_family_of( $reference_id ) === $family ); 141 169 $path = get_attached_file( $reference_id ); 142 170 $name = ( is_string( $path ) && $path !== '' ) ? wp_basename( $path ) : get_the_title( $reference_id ); … … 148 176 'edit_url' => is_string( $edit ) ? $edit : '', 149 177 'library_url' => add_query_arg( array( 'item' => $reference_id ), admin_url( 'upload.php' ) ), 150 'is_reference' => ( $reference_id === $attachment_id ),178 'is_reference' => $is_reference, 151 179 'copies' => count( $ids ) - 1, 152 180 'ids' => $ids, … … 390 418 391 419 /** 392 * Identity used by the ALT audit to group images: two copies of the same393 * file share one identity, an unscannedimage is its own identity.420 * Identity used by the ALT audit to group images: copies of the same file and 421 * translations of one media share one identity, any other image is its own identity. 394 422 * 395 423 * @param int $attachment_id Attachment ID. 396 * @return string " h:<md5>" or "id:<ID>".424 * @return string "f:<set>", "h:<md5>" or "id:<ID>". 397 425 */ 398 426 public static function identity_for_attachment( $attachment_id ) { … … 408 436 */ 409 437 public static function identity_from_hash( $attachment_id, $hash ) { 438 $attachment_id = (int) $attachment_id; 439 $map = self::get_identity_map(); 440 if ( isset( $map[ $attachment_id ] ) ) { 441 return $map[ $attachment_id ]; 442 } 410 443 if ( is_string( $hash ) && strlen( $hash ) === 32 ) { 411 444 return 'h:' . $hash; 412 445 } 413 // Not fingerprinted yet: the translations of one media (WPML, Polylang, Bogo) still 414 // count as a single image, so the same ALT on its FR/EN/ES versions is not "duplicated". 415 $family = self::get_translation_family_of( (int) $attachment_id ); 416 return $family !== '' ? 'f:' . $family : 'id:' . (int) $attachment_id; 446 return 'id:' . $attachment_id; 447 } 448 449 /** 450 * Groups the attachments linked by a common file (same hash) or by a translation link 451 * (WPML, Polylang, Bogo), transitively, so the same ALT on them is never "duplicated". 452 * 453 * The identity used to be the hash when there was one, the translation set otherwise. A WPML 454 * original is fingerprinted on upload but its language copies are not (they get their file 455 * after add_attachment): the original counted as "h:<md5>", each copy as "f:<set>", and 456 * giving the copies their ALT turned the whole set into "Duplicated ALT". 457 * 458 * @return array<int, string> Attachment ID => identity, only for attachments that have a hash or a translation. 459 */ 460 private static function get_identity_map() { 461 if ( null !== self::$identity_map ) { 462 return self::$identity_map; 463 } 464 global $wpdb; 465 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One query for all hashes, cached per request; the ALT audit using it is cached as a transient. 466 $rows = $wpdb->get_results( 467 $wpdb->prepare( 468 "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s", 469 self::META_HASH 470 ) 471 ); 472 473 // Union-find on the nodes "id:N", "h:<md5>" and "f:<set>". The smallest node name becomes 474 // the root, so a group keeps the same identity from one request to the next. 475 $parent = array(); 476 $find = static function ( $node ) use ( &$parent ) { 477 $root = $node; 478 while ( $parent[ $root ] !== $root ) { 479 $root = $parent[ $root ]; 480 } 481 while ( $parent[ $node ] !== $root ) { 482 $next = $parent[ $node ]; 483 $parent[ $node ] = $root; 484 $node = $next; 485 } 486 return $root; 487 }; 488 $union = static function ( $a, $b ) use ( &$parent, $find ) { 489 if ( ! isset( $parent[ $a ] ) ) { 490 $parent[ $a ] = $a; 491 } 492 if ( ! isset( $parent[ $b ] ) ) { 493 $parent[ $b ] = $b; 494 } 495 $root_a = $find( $a ); 496 $root_b = $find( $b ); 497 if ( $root_a === $root_b ) { 498 return; 499 } 500 if ( strcmp( $root_a, $root_b ) < 0 ) { 501 $parent[ $root_b ] = $root_a; 502 } else { 503 $parent[ $root_a ] = $root_b; 504 } 505 }; 506 507 if ( is_array( $rows ) ) { 508 foreach ( $rows as $row ) { 509 if ( is_string( $row->meta_value ) && strlen( $row->meta_value ) === 32 ) { 510 $union( 'id:' . (int) $row->post_id, 'h:' . $row->meta_value ); 511 } 512 } 513 } 514 foreach ( self::get_translation_family_map() as $id => $family ) { 515 $union( 'id:' . (int) $id, 'f:' . $family ); 516 } 517 518 // "f:" and "h:" sort before "id:": a linked attachment always gets a file or set identity. 519 $map = array(); 520 foreach ( array_keys( $parent ) as $node ) { 521 if ( strpos( $node, 'id:' ) === 0 ) { 522 $map[ (int) substr( $node, 3 ) ] = $find( $node ); 523 } 524 } 525 self::$identity_map = $map; 526 return $map; 417 527 } 418 528 … … 488 598 */ 489 599 public static function invalidate_cache() { 600 self::$identity_map = null; 490 601 delete_transient( self::GROUPS_TRANSIENT ); 602 if ( class_exists( 'Filikod_Visual_Duplicates' ) ) { 603 // Similar images skip exact copies and group translations: both depend on hashes and links. 604 Filikod_Visual_Duplicates::invalidate_cache(); 605 } 491 606 if ( class_exists( 'Filikod_Alt_Audit' ) ) { 492 607 Filikod_Alt_Audit::invalidate_cache(); … … 513 628 self::invalidate_cache(); 514 629 } elseif ( '_original_post' === $meta_key ) { 515 // Bogo : lien de traduction posé ou modifié .516 delete_transient( self::GROUPS_TRANSIENT);630 // Bogo : lien de traduction posé ou modifié (groupes et identités de l'audit ALT). 631 self::invalidate_cache(); 517 632 } 518 633 } … … 528 643 self::invalidate_cache(); 529 644 } elseif ( '_original_post' === $meta_key ) { 530 delete_transient( self::GROUPS_TRANSIENT);645 self::invalidate_cache(); 531 646 } 532 647 } … … 553 668 return 'polylang'; 554 669 } 555 if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {670 if ( defined( 'ICL_SITEPRESS_VERSION' ) || class_exists( 'SitePress', false ) ) { 556 671 return 'wpml'; 557 672 } … … 593 708 break; 594 709 case 'wpml': 595 $table = $wpdb->prefix . 'icl_translations';710 $table = esc_sql( $wpdb->prefix . 'icl_translations' ); 596 711 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- WPML core table, name derived from $wpdb->prefix; one-shot map cached per request. 597 712 $rows = $wpdb->get_results( "SELECT element_id, trid FROM {$table} WHERE element_type = 'post_attachment'" ); … … 656 771 break; 657 772 case 'wpml': 773 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML public API filter. 658 774 $trid = apply_filters( 'wpml_element_trid', null, $attachment_id, 'post_attachment' ); 659 775 if ( $trid ) { 776 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML public API filter. 660 777 $translations = apply_filters( 'wpml_get_element_translations', null, $trid, 'post_attachment' ); 661 778 if ( is_array( $translations ) ) { … … 720 837 721 838 /** 839 * Map attachment ID => language code (lowercase, e.g. "fr"), one query per engine, cached for the 840 * request. Used by the similar images to tell a duplicate (same language) from a language version. 841 * 842 * @return array<int, string> Empty when no multilingual engine assigns languages to media. 843 */ 844 public static function get_language_map() { 845 static $map = null; 846 if ( null !== $map ) { 847 return $map; 848 } 849 global $wpdb; 850 $map = array(); 851 $rows = array(); 852 switch ( self::get_multilingual_engine() ) { 853 case 'polylang': 854 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-shot map, cached per request. 855 $rows = $wpdb->get_results( 856 "SELECT tr.object_id AS id, t.slug AS lang 857 FROM {$wpdb->term_relationships} tr 858 INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id 859 INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id 860 INNER JOIN {$wpdb->posts} p ON p.ID = tr.object_id 861 WHERE tt.taxonomy = 'language' AND p.post_type = 'attachment'" 862 ); 863 break; 864 case 'wpml': 865 $table = esc_sql( $wpdb->prefix . 'icl_translations' ); 866 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- WPML core table, name derived from $wpdb->prefix; one-shot map cached per request. 867 $rows = $wpdb->get_results( "SELECT element_id AS id, language_code AS lang FROM {$table} WHERE element_type = 'post_attachment'" ); 868 break; 869 case 'bogo': 870 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-shot map, cached per request. 871 $rows = $wpdb->get_results( 872 $wpdb->prepare( 873 "SELECT pm.post_id AS id, pm.meta_value AS lang FROM {$wpdb->postmeta} pm 874 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id 875 WHERE pm.meta_key = %s AND p.post_type = 'attachment'", 876 '_locale' 877 ) 878 ); 879 break; 880 } 881 if ( is_array( $rows ) ) { 882 foreach ( $rows as $row ) { 883 $lang = strtolower( substr( (string) $row->lang, 0, 2 ) ); 884 if ( $lang !== '' ) { 885 $map[ (int) $row->id ] = $lang; 886 } 887 } 888 } 889 return $map; 890 } 891 892 /** 722 893 * Language label of an attachment, empty when the multilingual plugin does not assign 723 894 * a language to media (Weglot, GTranslate, TranslatePress, WPGlobus, MultilingualPress). … … 736 907 break; 737 908 case 'wpml': 909 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML public API filter. 738 910 $details = apply_filters( 'wpml_post_language_details', null, $attachment_id ); 739 911 if ( is_array( $details ) && ! empty( $details['language_code'] ) ) { … … 762 934 */ 763 935 public static function on_translations_linked( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) { 764 if ( 'post_translations' === $taxonomy ) { 936 if ( 'post_translations' !== $taxonomy ) { 937 return; 938 } 939 // Les identités de l'audit ALT dépendent des liens entre médias seulement : la traduction 940 // d'un article ne doit pas faire recalculer l'audit de toute la médiathèque. 941 if ( 'attachment' === get_post_type( (int) $object_id ) ) { 942 self::invalidate_cache(); 943 } else { 765 944 delete_transient( self::GROUPS_TRANSIENT ); 766 945 } … … 769 948 /** 770 949 * WPML: a translation link was created or updated. 771 */ 772 public static function on_wpml_translation_update() { 773 delete_transient( self::GROUPS_TRANSIENT ); 950 * 951 * @param array $args Translation details sent by WPML (element_type among others). 952 */ 953 public static function on_wpml_translation_update( $args = array() ) { 954 if ( is_array( $args ) && isset( $args['element_type'] ) && 'post_attachment' !== $args['element_type'] ) { 955 delete_transient( self::GROUPS_TRANSIENT ); 956 return; 957 } 958 self::invalidate_cache(); 774 959 } 775 960 … … 882 1067 foreach ( $ids as $id ) { 883 1068 $result = self::hash_attachment( (int) $id, (bool) $force ); 884 if ( 'hashed' === $result ) { 1069 // Same pass for the visual signature of the "Similar images" tab. 1070 $visual = class_exists( 'Filikod_Visual_Duplicates' ) 1071 ? Filikod_Visual_Duplicates::sign_attachment( (int) $id, (bool) $force ) 1072 : 'skipped'; 1073 if ( 'hashed' === $result || 'signed' === $visual ) { 885 1074 $processed++; 886 1075 } else { … … 986 1175 foreach ( array_chunk( $hashes, 200 ) as $chunk ) { 987 1176 $placeholders = implode( ',', array_fill( 0, count( $chunk ), '%s' ) ); 988 // phpcs: ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- Placeholders generated forthe IN() list; values passed to prepare().1177 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- One %s generated per value of the IN() list; values passed to prepare(). 989 1178 $rows = $wpdb->get_results( 990 1179 $wpdb->prepare( … … 1001 1190 ) 1002 1191 ); 1192 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber 1003 1193 if ( is_array( $rows ) ) { 1004 1194 foreach ( $rows as $row ) { -
filikod/trunk/languages/filikod-fr_FR.po
r3737001 r3763820 1 1 msgid "" 2 2 msgstr "" 3 "Project-Id-Version: Filikod 1. 2.1\n"3 "Project-Id-Version: Filikod 1.3.0\n" 4 4 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/filikod\n" 5 5 "PO-Revision-Date: 2026-10-06 12:00+0000\n" … … 13 13 "X-Domain: filikod\n" 14 14 15 #: admin/views/alt-audit.php:111 15 #: admin/views/alt-audit.php:109 16 msgid "With WPGlobus or qTranslate-XT, an image is also listed here when its ALT is empty in one of the site languages (see the Language column)." 17 msgstr "Avec WPGlobus ou qTranslate-XT, une image apparaît aussi ici quand son ALT est vide dans l’une des langues du site (voir la colonne Langue)." 18 19 #: admin/views/alt-audit.php:122 20 #: includes/admin/class-filikod-admin.php:68 16 21 #: includes/admin/class-filikod-admin.php:69 17 #: includes/admin/class-filikod-admin.php:7018 22 msgid "ALT Audit" 19 23 msgstr "Audit ALT" 20 24 21 #: admin/views/alt-audit.php:1 1725 #: admin/views/alt-audit.php:128 22 26 msgid "Filter by issue type" 23 27 msgstr "Trier par type de problème" 24 28 25 #: admin/views/alt-audit.php:1 4729 #: admin/views/alt-audit.php:158 26 30 msgid "No images match this filter." 27 31 msgstr "Aucune image ne correspond à ce filtre." 28 32 29 #: admin/views/alt-audit.php:152 30 #: admin/views/duplicates.php:167 31 #: admin/views/duplicates.php:276 33 #: admin/views/alt-audit.php:163 34 #: admin/views/duplicates-similar.php:88 35 #: admin/views/duplicates.php:192 36 #: admin/views/duplicates.php:308 32 37 msgid "Thumbnail" 33 38 msgstr "Miniature" 34 39 35 #: admin/views/alt-audit.php:153 36 #: admin/views/duplicates.php:277 40 #: admin/views/alt-audit.php:164 41 #: admin/views/duplicates-similar.php:89 42 #: admin/views/duplicates.php:309 37 43 msgid "File name" 38 44 msgstr "Nom du fichier" 39 45 40 #: admin/views/alt-audit.php:154 41 #: admin/views/duplicates.php:169 42 #: admin/views/duplicates.php:278 46 #: admin/views/alt-audit.php:166 47 msgid "Language" 48 msgstr "Langue" 49 50 #: admin/views/alt-audit.php:168 51 #: admin/views/duplicates-similar.php:90 52 #: admin/views/duplicates.php:194 53 #: admin/views/duplicates.php:310 43 54 msgid "Context" 44 55 msgstr "Contexte" 45 56 46 #: admin/views/alt-audit.php:155 47 #: admin/views/duplicates.php:170 48 #: admin/views/duplicates.php:279 57 #: admin/views/alt-audit.php:169 58 #: admin/views/duplicates-similar.php:91 59 #: admin/views/duplicates.php:195 60 #: admin/views/duplicates.php:311 49 61 msgid "ALT" 50 62 msgstr "ALT" 51 63 52 #: admin/views/alt-audit.php:156 53 #: admin/views/duplicates.php:280 64 #: admin/views/alt-audit.php:170 65 #: admin/views/duplicates-similar.php:92 66 #: admin/views/duplicates.php:312 54 67 msgid "Issue" 55 68 msgstr "Problème" 56 69 57 #: admin/views/alt-audit.php:158 58 #: admin/views/duplicates.php:171 59 #: admin/views/duplicates.php:281 70 #: admin/views/alt-audit.php:172 71 #: admin/views/duplicates-similar.php:93 72 #: admin/views/duplicates.php:196 73 #: admin/views/duplicates.php:313 60 74 msgid "Action" 61 75 msgstr "Action" 62 76 63 #: admin/views/alt-audit.php:211 77 #: admin/views/alt-audit.php:231 78 msgid "empty: %s" 79 msgstr "vide : %s" 80 81 #: admin/views/alt-audit.php:240 82 msgid "Not translated" 83 msgstr "Non traduit" 84 85 #: admin/views/alt-audit.php:252 64 86 msgid "ALT text for this image" 65 87 msgstr "Texte ALT pour cette image" 66 88 67 #: admin/views/alt-audit.php:2 1189 #: admin/views/alt-audit.php:252 68 90 msgid "Decorative image (alt=\"\")" 69 91 msgstr "Image décorative (alt=\"\")" 70 92 71 #: admin/views/alt-audit.php:2 4093 #: admin/views/alt-audit.php:281 72 94 msgid "Mark this image as a regular content image (requires an ALT text)." 73 95 msgstr "Marquer cette image comme image de contenu normale (un texte ALT est requis)." 74 96 75 #: admin/views/alt-audit.php:2 4097 #: admin/views/alt-audit.php:281 76 98 msgid "Mark this image as decorative (alt=\"\", excluded from the ALT score)." 77 99 msgstr "Marquer cette image comme décorative (alt=\"\", exclue du score ALT)." 78 100 79 #: admin/views/alt-audit.php:2 52101 #: admin/views/alt-audit.php:293 80 102 msgid "Save" 81 103 msgstr "Enregistrer" 82 104 83 #: admin/views/alt-audit.php:268 84 #: admin/views/duplicates.php:224 85 #: admin/views/duplicates.php:375 105 #: admin/views/alt-audit.php:309 106 #: admin/views/duplicates-similar.php:185 107 #: admin/views/duplicates.php:249 108 #: admin/views/duplicates.php:407 86 109 msgid "Pagination" 87 110 msgstr "Pagination" 88 111 89 #: admin/views/alt-audit.php: 27390 #: admin/views/duplicates.php:2 29112 #: admin/views/alt-audit.php:314 113 #: admin/views/duplicates.php:254 91 114 msgid "Page %1$d of %2$d (%3$d items)" 92 115 msgstr "Page %1$d sur %2$d (%3$d éléments)" 93 116 94 #: admin/views/alt-audit.php:282 95 #: admin/views/duplicates.php:238 96 #: admin/views/duplicates.php:389 117 #: admin/views/alt-audit.php:323 118 #: admin/views/duplicates-similar.php:199 119 #: admin/views/duplicates.php:263 120 #: admin/views/duplicates.php:421 97 121 msgid "« Previous" 98 122 msgstr "« Précédent" 99 123 100 #: admin/views/alt-audit.php:285 101 #: admin/views/duplicates.php:241 102 #: admin/views/duplicates.php:392 124 #: admin/views/alt-audit.php:326 125 #: admin/views/duplicates-similar.php:202 126 #: admin/views/duplicates.php:266 127 #: admin/views/duplicates.php:424 103 128 msgid "Next »" 104 129 msgstr "Suivant »" … … 106 131 #: admin/views/alt-audit.php:67 107 132 #: admin/views/dashboard.php:103 108 #: includes/admin/class-filikod-media-alt-status.php:30 2133 #: includes/admin/class-filikod-media-alt-status.php:303 109 134 msgid "Missing" 110 135 msgstr "Manquant" … … 112 137 #: admin/views/alt-audit.php:68 113 138 #: admin/views/dashboard.php:107 114 #: includes/admin/class-filikod-media-alt-status.php:30 4139 #: includes/admin/class-filikod-media-alt-status.php:305 115 140 msgid "Generic" 116 141 msgstr "Générique" … … 118 143 #: admin/views/alt-audit.php:69 119 144 #: admin/views/dashboard.php:111 120 #: includes/admin/class-filikod-media-alt-status.php:30 6145 #: includes/admin/class-filikod-media-alt-status.php:307 121 146 msgid "Too short" 122 147 msgstr "Trop court" … … 124 149 #: admin/views/alt-audit.php:70 125 150 #: admin/views/dashboard.php:115 126 #: includes/admin/class-filikod-media-alt-status.php:30 8151 #: includes/admin/class-filikod-media-alt-status.php:309 127 152 msgid "Too long" 128 153 msgstr "Trop long" … … 130 155 #: admin/views/alt-audit.php:71 131 156 #: admin/views/dashboard.php:119 132 #: includes/admin/class-filikod-media-alt-status.php:31 0157 #: includes/admin/class-filikod-media-alt-status.php:311 133 158 msgid "Duplicated" 134 159 msgstr "Dupliqué" 135 160 136 161 #: admin/views/alt-audit.php:72 137 #: admin/views/alt-audit.php:1 57138 #: admin/views/alt-audit.php:2 42162 #: admin/views/alt-audit.php:171 163 #: admin/views/alt-audit.php:283 139 164 #: admin/views/dashboard.php:123 140 #: includes/admin/class-filikod-media-alt-status.php:31 2165 #: includes/admin/class-filikod-media-alt-status.php:313 141 166 msgid "Decorative" 142 167 msgstr "Décorative" … … 210 235 #: includes/dashboard/class-filikod-dashboard.php:161 211 236 #: includes/dashboard/class-filikod-dashboard.php:168 212 #: includes/dashboard/class-filikod-dashboard.php:200 213 #: includes/dashboard/class-filikod-dashboard.php:207 214 #: includes/duplicates/class-filikod-duplicates.php:905 215 #: includes/duplicates/class-filikod-duplicates.php:918 237 #: includes/dashboard/class-filikod-dashboard.php:202 238 #: includes/dashboard/class-filikod-dashboard.php:209 239 #: includes/duplicates/class-filikod-duplicates.php:1091 240 #: includes/duplicates/class-filikod-duplicates.php:1104 241 #: includes/duplicates/class-filikod-visual-duplicates.php:742 216 242 #: includes/settings/class-filikod-settings.php:253 217 243 #: includes/settings/class-filikod-settings.php:275 … … 282 308 msgstr "Score ALT" 283 309 284 #: admin/views/duplicates.php:104 310 #: admin/views/duplicates-similar.php:108 311 msgid "Group %1$d: %2$d similar images" 312 msgstr "Groupe %1$d : %2$d images semblables" 313 314 #: admin/views/duplicates-similar.php:114 315 msgid "Show again" 316 msgstr "Afficher de nouveau" 317 318 #: admin/views/duplicates-similar.php:125 319 #: admin/views/duplicates.php:204 320 #: admin/views/duplicates.php:348 321 #: includes/duplicates/class-filikod-duplicates.php:248 322 msgid "ID %d" 323 msgstr "ID %d" 324 325 #: admin/views/duplicates-similar.php:156 326 #: admin/views/duplicates.php:229 327 #: admin/views/duplicates.php:378 328 msgid "No ALT" 329 msgstr "Sans ALT" 330 331 #: admin/views/duplicates-similar.php:164 332 #: admin/views/duplicates.php:233 333 #: admin/views/duplicates.php:386 334 msgid "Edit" 335 msgstr "Modifier" 336 337 #: admin/views/duplicates-similar.php:166 338 #: admin/views/duplicates.php:235 339 #: admin/views/duplicates.php:388 340 msgid "Library" 341 msgstr "Médiathèque" 342 343 #: admin/views/duplicates-similar.php:17 344 msgid "Same language" 345 msgstr "Même langue" 346 347 #: admin/views/duplicates-similar.php:17 348 msgid "To check" 349 msgstr "À vérifier" 350 351 #: admin/views/duplicates-similar.php:18 352 msgid "Other languages" 353 msgstr "Autres langues" 354 355 #: admin/views/duplicates-similar.php:19 356 #: admin/views/duplicates-similar.php:114 357 msgid "Intentional" 358 msgstr "C’est voulu" 359 360 #: admin/views/duplicates-similar.php:190 361 #: admin/views/duplicates.php:412 362 msgid "Page %1$d of %2$d (%3$d groups)" 363 msgstr "Page %1$d sur %2$d (%3$d groupes)" 364 365 #: admin/views/duplicates-similar.php:27 366 msgid "Oldest upload" 367 msgstr "Premier envoi" 368 369 #: admin/views/duplicates-similar.php:28 370 msgid "Similar" 371 msgstr "Semblable" 372 373 #: admin/views/duplicates-similar.php:31 374 msgid "Uploaded first among these images." 375 msgstr "Envoyée en premier parmi ces images." 376 377 #: admin/views/duplicates-similar.php:32 378 msgid "Shows the same picture as the first image but is a different file (re-export, compression, new name). Check the Context column before removing it from the Media Library." 379 msgstr "Montre la même photo que la première image, mais dans un autre fichier (réexport, compression, nouveau nom). Vérifiez la colonne Contexte avant de la retirer de la médiathèque." 380 381 #: admin/views/duplicates-similar.php:39 382 msgid "Look-alike images uploaded in different languages, for example a map with French labels and the same map with English labels. They are usually intentional language versions, so they stay out of the main list. Link them as translations in your multilingual plugin, or mark them as intentional." 383 msgstr "Images semblables envoyées dans des langues différentes, par exemple une carte aux libellés en français et la même carte en anglais. Ce sont en général des versions de langue voulues : elles restent hors de la liste principale. Reliez-les comme traductions dans votre extension multilingue, ou marquez-les comme voulues." 384 385 #: admin/views/duplicates-similar.php:41 386 msgid "Groups you marked as intentional. They stay out of the list until an image is added to or removed from the group." 387 msgstr "Groupes que vous avez marqués comme voulus. Ils restent hors de la liste tant qu’aucune image n’est ajoutée au groupe ou retirée du groupe." 388 389 #: admin/views/duplicates-similar.php:43 390 msgid "The same photo saved as different files: re-exported, compressed or renamed before being uploaded again. Filikod compares what the images show, not the files, and only lists images of the same dimensions: a banner and its smaller version are never listed. Check where each one is used, keep that one, and remove the others from the Media Library once you are sure. Nothing is deleted automatically." 391 msgstr "La même photo enregistrée dans des fichiers différents : réexportée, compressée ou renommée avant d’être envoyée de nouveau. Filikod compare ce que montrent les images, pas les fichiers. Il ne liste que des images de mêmes dimensions : une bannière et sa version réduite ne sont jamais listées. Vérifiez où chacune est utilisée, gardez celle-là, puis retirez les autres de la médiathèque une fois sûr de vous. Rien n’est supprimé automatiquement." 392 393 #: admin/views/duplicates-similar.php:49 394 msgid "Your server has no GD image library, so Filikod cannot compare images. Ask your host to enable the PHP GD extension." 395 msgstr "Votre serveur n’a pas la bibliothèque d’images GD : Filikod ne peut donc pas comparer les images. Demandez à votre hébergeur d’activer l’extension PHP GD." 396 397 #: admin/views/duplicates-similar.php:55 398 msgid "%1$s of %2$s images have a visual signature; %3$s cannot be compared (SVG, icons or missing files)." 399 msgstr "%1$s images sur %2$s ont une signature visuelle ; %3$s ne peuvent pas être comparées (SVG, icônes ou fichiers manquants)." 400 401 #: admin/views/duplicates-similar.php:81 402 msgid "No visual signature yet. Click \"Scan library\" above to compute them." 403 msgstr "Aucune signature visuelle pour l’instant. Cliquez sur « Analyser la médiathèque » ci-dessus pour les calculer." 404 405 #: admin/views/duplicates-similar.php:83 406 msgid "No similar images in this list." 407 msgstr "Aucune image semblable dans cette liste." 408 409 #: admin/views/duplicates.php:107 410 msgid "%1$s of %2$s images fingerprinted." 411 msgstr "%1$s images sur %2$s ont une empreinte." 412 413 #: admin/views/duplicates.php:115 285 414 msgid "%s images are still waiting. \"Scan library\" fingerprints them; \"Full rescan\" recomputes every fingerprint, useful after files were replaced outside WordPress." 286 415 msgstr "%s images sont encore en attente. « Analyser la médiathèque » calcule leur empreinte ; « Analyse complète » recalcule toutes les empreintes, utile après un remplacement de fichiers hors de WordPress." 287 416 288 #: admin/views/duplicates.php:1 08417 #: admin/views/duplicates.php:119 289 418 msgid "Every image is fingerprinted. \"Full rescan\" recomputes every fingerprint, useful after files were replaced outside WordPress." 290 419 msgstr "Toutes les images ont une empreinte. « Analyse complète » recalcule toutes les empreintes, utile après un remplacement de fichiers hors de WordPress." 291 420 292 #: admin/views/duplicates.php:1 14421 #: admin/views/duplicates.php:125 293 422 msgid "%s images have no file on disk (broken attachments) and cannot be fingerprinted." 294 423 msgstr "%s images n’ont aucun fichier sur le disque (fichiers joints cassés) et ne peuvent pas recevoir d’empreinte." 295 424 296 #: admin/views/duplicates.php:121 425 #: admin/views/duplicates.php:134 426 msgid "%s images have no visual signature yet (Similar images tab): \"Scan library\" computes them." 427 msgstr "%s images n’ont pas encore de signature visuelle (onglet Images semblables) : « Analyser la médiathèque » les calcule." 428 429 #: admin/views/duplicates.php:141 297 430 msgid "Scan library" 298 431 msgstr "Analyser la médiathèque" 299 432 300 #: admin/views/duplicates.php:1 24433 #: admin/views/duplicates.php:144 301 434 msgid "Full rescan" 302 435 msgstr "Analyse complète" 303 436 304 #: admin/views/duplicates.php:1 30437 #: admin/views/duplicates.php:150 305 438 #: admin/views/settings.php:143 306 439 #: admin/views/settings.php:294 … … 308 441 msgstr "Important :" 309 442 310 #: admin/views/duplicates.php:1 31443 #: admin/views/duplicates.php:151 311 444 #: admin/views/settings.php:144 312 445 #: admin/views/settings.php:295 … … 314 447 msgstr "Veuillez conserver cette page ouverte jusqu’à ce que le processus soit terminé. Ne naviguez pas ailleurs ou fermez cette page." 315 448 316 #: admin/views/duplicates.php:1 46449 #: admin/views/duplicates.php:166 317 450 msgid "Duplicated files" 318 451 msgstr "Fichiers en double" 319 452 320 #: admin/views/duplicates.php:149 453 #: admin/views/duplicates.php:169 454 msgid "Similar images" 455 msgstr "Images semblables" 456 457 #: admin/views/duplicates.php:172 321 458 msgid "Missing files" 322 459 msgstr "Fichiers manquants" 323 460 324 #: admin/views/duplicates.php:1 59461 #: admin/views/duplicates.php:184 325 462 msgid "Image attachments whose file could not be found on this server: the scan could not fingerprint them. They still appear in the Media Library and may be referenced in your content. If your media is offloaded to external storage (S3, CDN), these files are simply not fingerprinted and display normally. Otherwise they render as broken images: restore the file before deleting anything from the Media Library." 326 463 msgstr "Images dont le fichier est introuvable sur ce serveur : l’analyse n’a pas pu calculer leur empreinte. Elles apparaissent toujours dans la médiathèque et peuvent être utilisées dans vos contenus. Si vos médias sont déportés sur un stockage externe (S3, CDN), ces fichiers n’ont simplement pas d’empreinte et s’affichent normalement. Sinon, ils s’affichent comme des images cassées : restaurez le fichier avant de supprimer quoi que ce soit dans la médiathèque." 327 464 328 #: admin/views/duplicates.php:1 62465 #: admin/views/duplicates.php:187 329 466 msgid "No missing file among the scanned images." 330 467 msgstr "Aucun fichier manquant parmi les images analysées." 331 468 332 #: admin/views/duplicates.php:1 68469 #: admin/views/duplicates.php:193 333 470 msgid "Expected file" 334 471 msgstr "Fichier attendu" 335 472 336 #: admin/views/duplicates.php:179 337 #: admin/views/duplicates.php:316 338 #: includes/duplicates/class-filikod-duplicates.php:220 339 msgid "ID %d" 340 msgstr "ID %d" 341 342 #: admin/views/duplicates.php:204 343 #: admin/views/duplicates.php:346 344 msgid "No ALT" 345 msgstr "Sans ALT" 346 347 #: admin/views/duplicates.php:208 348 #: admin/views/duplicates.php:354 349 msgid "Edit" 350 msgstr "Modifier" 351 352 #: admin/views/duplicates.php:210 353 #: admin/views/duplicates.php:356 354 msgid "Library" 355 msgstr "Médiathèque" 356 357 #: admin/views/duplicates.php:253 358 msgid "Exact copies of the same file (same md5 fingerprint) uploaded more than once. Nothing is deleted or merged: check where each copy is used, keep that one, and remove the others from the Media Library once you are sure. Media linked as translations of each other (Polylang, WPML, Bogo) are intentional and never listed here." 359 msgstr "Copies exactes d’un même fichier (même empreinte md5) envoyées plusieurs fois. Rien n’est supprimé ni fusionné : vérifiez où chaque copie est utilisée, gardez celle-là et retirez les autres de la médiathèque une fois sûr de vous. Les médias liés comme traductions les uns des autres (Polylang, WPML, Bogo) sont volontaires et ne sont jamais listés ici." 360 361 #: admin/views/duplicates.php:257 473 #: admin/views/duplicates.php:279 474 msgid "Exact copies of the same file (same md5 fingerprint) uploaded more than once. Nothing is deleted or merged: check where each copy is used, keep that one, and remove the others from the Media Library once you are sure." 475 msgstr "Copies exactes d’un même fichier (même empreinte md5) envoyées plusieurs fois. Rien n’est supprimé ni fusionné : vérifiez où chaque copie est utilisée, gardez celle-là et retirez les autres de la médiathèque une fois sûr de vous." 476 477 #: admin/views/duplicates.php:283 478 msgid "Media linked as translations of each other (Polylang, WPML, Bogo) are intentional and never listed here." 479 msgstr "Les médias liés comme traductions les uns des autres (Polylang, WPML, Bogo) sont volontaires et ne sont jamais listés ici." 480 481 #: admin/views/duplicates.php:289 362 482 msgid "No scan yet. Click \"Scan library\" above to fingerprint your images." 363 483 msgstr "Aucune analyse pour l’instant. Cliquez sur « Analyser la médiathèque » ci-dessus pour calculer l’empreinte de vos images." 364 484 365 #: admin/views/duplicates.php:2 59485 #: admin/views/duplicates.php:291 366 486 msgid "No duplicate images found among the fingerprinted files." 367 487 msgstr "Aucune image en double parmi les fichiers analysés." 368 488 369 #: admin/views/duplicates.php:2 65489 #: admin/views/duplicates.php:297 370 490 msgid "%1$s groups, %2$s redundant files, %3$s that could be freed by keeping a single copy per group." 371 491 msgstr "%1$s groupes, %2$s fichiers redondants, %3$s libérables en ne gardant qu’une copie par groupe." 372 492 373 #: admin/views/duplicates.php:3 01493 #: admin/views/duplicates.php:333 374 494 msgid "Group %1$d: %2$d identical copies, %3$s could be freed" 375 495 msgstr "Groupe %1$d : %2$d copies identiques, %3$s libérables" 376 496 377 #: admin/views/duplicates.php: 37497 #: admin/views/duplicates.php:48 378 498 msgid "Reference" 379 499 msgstr "Référence" 380 500 381 #: admin/views/duplicates.php: 38382 #: includes/duplicates/class-filikod-duplicates.php:2 40501 #: admin/views/duplicates.php:49 502 #: includes/duplicates/class-filikod-duplicates.php:268 383 503 msgid "Duplicate" 384 504 msgstr "Doublon" 385 505 386 #: admin/views/duplicates.php:380 387 msgid "Page %1$d of %2$d (%3$d groups)" 388 msgstr "Page %1$d sur %2$d (%3$d groupes)" 389 390 #: admin/views/duplicates.php:41 506 #: admin/views/duplicates.php:52 391 507 msgid "Oldest copy of this file: the one to keep, unless another copy is the one actually used in your content." 392 508 msgstr "Copie la plus ancienne de ce fichier : celle à garder, sauf si une autre copie est celle réellement utilisée dans vos contenus." 393 509 394 #: admin/views/duplicates.php: 42510 #: admin/views/duplicates.php:53 395 511 msgid "Exact copy (same md5 fingerprint) of an image uploaded earlier. Check the Context column before removing it from the Media Library." 396 512 msgstr "Copie exacte (même empreinte md5) d’une image envoyée plus tôt. Vérifiez la colonne Contexte avant de la retirer de la médiathèque." 397 513 398 #: admin/views/duplicates.php: 83399 #: admin/views/duplicates.php:1 44400 #: includes/admin/class-filikod-admin.php:8 1514 #: admin/views/duplicates.php:94 515 #: admin/views/duplicates.php:164 516 #: includes/admin/class-filikod-admin.php:80 401 517 msgid "Duplicates" 402 518 msgstr "Doublons" 403 519 404 #: admin/views/duplicates.php: 88520 #: admin/views/duplicates.php:99 405 521 msgid "Filikod fingerprints the original file of every image (md5) to find exact copies. New uploads are fingerprinted automatically; run the scan once for the images already in your library." 406 522 msgstr "Filikod calcule l’empreinte (md5) du fichier original de chaque image pour trouver les copies exactes. Les nouveaux envois reçoivent leur empreinte automatiquement ; lancez l’analyse une fois pour les images déjà présentes dans votre médiathèque." 407 408 #: admin/views/duplicates.php:96409 msgid "%1$s of %2$s images fingerprinted."410 msgstr "%1$s images sur %2$s ont une empreinte."411 523 412 524 #: admin/views/settings.php:101 … … 571 683 msgstr "Définissez la largeur maximale en pixels pour les images téléversées. Les images plus larges que cette largeur seront automatiquement redimensionnées tout en conservant leur proportion de taille. Recommandé : 2000px pour la plupart des sites." 572 684 573 #: filikod.php:2 51685 #: filikod.php:261 574 686 msgid "Filikod requires WordPress 5.8 or higher." 575 687 msgstr "Filikod nécessite une version de WordPress 5.8 ou supérieure." 576 688 577 #: filikod.php:2 61689 #: filikod.php:271 578 690 msgid "Filikod requires PHP 7.4 or higher." 579 691 msgstr "Filikod nécessite une version de PHP 7.4 ou supérieure." 580 692 581 #: includes/admin/class-filikod-admin.php:14 2693 #: includes/admin/class-filikod-admin.php:141 582 694 msgid "Settings saved successfully!" 583 695 msgstr "Les réglages ont bien été enregistrés !" 584 696 585 #: includes/admin/class-filikod-admin.php:14 3697 #: includes/admin/class-filikod-admin.php:142 586 698 msgid "Error saving settings." 587 699 msgstr "Erreur dans l’enregistrement des réglages." 588 700 589 #: includes/admin/class-filikod-admin.php:14 4701 #: includes/admin/class-filikod-admin.php:143 590 702 msgid "Processing..." 591 703 msgstr "Traitement en cours…" 592 704 593 #: includes/admin/class-filikod-admin.php:145 705 #: includes/admin/class-filikod-admin.php:144 706 #: includes/duplicates/class-filikod-visual-duplicates.php:750 594 707 msgid "An error occurred." 595 708 msgstr "Une erreur s’est produite." 596 709 597 #: includes/admin/class-filikod-admin.php:14 6710 #: includes/admin/class-filikod-admin.php:145 598 711 msgid "An error occurred during processing." 599 712 msgstr "Une erreur s’est produite durant le traitement." 600 713 601 #: includes/admin/class-filikod-admin.php:14 7714 #: includes/admin/class-filikod-admin.php:146 602 715 msgid "Starting processing..." 603 716 msgstr "Traitement en cours…" 604 717 605 #: includes/admin/class-filikod-admin.php:14 8718 #: includes/admin/class-filikod-admin.php:147 606 719 msgid "images resized" 607 720 msgstr "images retaillées" 608 721 609 #: includes/admin/class-filikod-admin.php:14 9722 #: includes/admin/class-filikod-admin.php:148 610 723 msgid "image resized" 611 724 msgstr "image retaillée" 612 725 613 #: includes/admin/class-filikod-admin.php:1 50726 #: includes/admin/class-filikod-admin.php:149 614 727 msgid "ignored" 615 728 msgstr "ignoré" 616 729 617 #: includes/admin/class-filikod-admin.php:15 1730 #: includes/admin/class-filikod-admin.php:150 618 731 msgid "saved" 619 732 msgstr "enregistré" 620 733 621 #: includes/admin/class-filikod-admin.php:15 3734 #: includes/admin/class-filikod-admin.php:152 622 735 msgid "Resize complete: %1$d images resized, %2$d ignored. Space saved: %3$s" 623 736 msgstr "Traitement validé : %1$d images traitées, %2$d ignorées. Gain de place : %3$s" 624 737 625 #: includes/admin/class-filikod-admin.php:15 4738 #: includes/admin/class-filikod-admin.php:153 626 739 msgid "Unable to retrieve image count" 627 740 msgstr "Impossible de récupérer le total des images" 628 741 629 #: includes/admin/class-filikod-admin.php:15 5742 #: includes/admin/class-filikod-admin.php:154 630 743 msgid "Error retrieving image count" 631 744 msgstr "Erreur lors de la récupération du total des images" 632 745 633 #: includes/admin/class-filikod-admin.php:15 6746 #: includes/admin/class-filikod-admin.php:155 634 747 msgid "image processed" 635 748 msgstr "image traitée" 636 749 637 #: includes/admin/class-filikod-admin.php:15 7750 #: includes/admin/class-filikod-admin.php:156 638 751 msgid "images processed" 639 752 msgstr "images traitées" 640 753 754 #: includes/admin/class-filikod-admin.php:157 641 755 #: includes/admin/class-filikod-admin.php:158 642 #: includes/admin/class-filikod-admin.php:159643 756 msgid "skipped" 644 757 msgstr "ignoré" 645 758 646 #: includes/admin/class-filikod-admin.php:16 1759 #: includes/admin/class-filikod-admin.php:160 647 760 msgid "Processing complete: %1$d images processed, %2$d skipped." 648 761 msgstr "Traitement validé : %1$d images traitées, %2$d ignorées." 649 762 650 #: includes/admin/class-filikod-admin.php:16 2763 #: includes/admin/class-filikod-admin.php:161 651 764 msgid "Saved." 652 765 msgstr "Enregistré." 653 766 654 #: includes/admin/class-filikod-admin.php:16 3767 #: includes/admin/class-filikod-admin.php:162 655 768 msgid "Error saving ALT." 656 769 msgstr "Erreur lors de l’enregistrement du ALT." 657 770 658 #: includes/admin/class-filikod-admin.php:16 4771 #: includes/admin/class-filikod-admin.php:163 659 772 msgid "Marked as decorative (excluded from the ALT score)." 660 773 msgstr "Marquée comme décorative (exclue du score ALT)." 661 774 662 #: includes/admin/class-filikod-admin.php:16 5775 #: includes/admin/class-filikod-admin.php:164 663 776 msgid "Image restored as a regular image, please add an ALT text." 664 777 msgstr "Image rétablie comme image normale, veuillez ajouter un texte ALT." 665 778 666 #: includes/admin/class-filikod-admin.php:16 7779 #: includes/admin/class-filikod-admin.php:166 667 780 msgid "%1$d fingerprinted, %2$d skipped (%3$d%%)" 668 781 msgstr "%1$d empreintes calculées, %2$d ignorées (%3$d %%)" 669 782 670 #: includes/admin/class-filikod-admin.php:16 9783 #: includes/admin/class-filikod-admin.php:168 671 784 msgid "Scan complete: %1$d images fingerprinted, %2$d duplicate groups found (%3$d redundant files). Reloading..." 672 785 msgstr "Analyse terminée : %1$d images avec empreinte, %2$d groupes de doublons trouvés (%3$d fichiers redondants). Rechargement…" 673 786 674 #: includes/admin/class-filikod-admin.php:4 7787 #: includes/admin/class-filikod-admin.php:46 675 788 msgid "Filikod" 676 789 msgstr "Filikod" 677 790 791 #: includes/admin/class-filikod-admin.php:58 678 792 #: includes/admin/class-filikod-admin.php:59 679 #: includes/admin/class-filikod-admin.php:60680 793 msgid "Dashboard" 681 794 msgstr "Tableau de bord" 682 795 683 #: includes/admin/class-filikod-admin.php: 80796 #: includes/admin/class-filikod-admin.php:79 684 797 msgid "Duplicate Images" 685 798 msgstr "Images en double" 686 799 800 #: includes/admin/class-filikod-admin.php:89 687 801 #: includes/admin/class-filikod-admin.php:90 688 #: includes/admin/class-filikod-admin.php:91689 802 msgid "Settings" 690 803 msgstr "Réglages" 691 804 692 #: includes/admin/class-filikod-media-alt-status.php:22 3693 #: includes/admin/class-filikod-media-alt-status.php:23 1805 #: includes/admin/class-filikod-media-alt-status.php:224 806 #: includes/admin/class-filikod-media-alt-status.php:232 694 807 msgid "Forbidden." 695 808 msgstr "Interdit." 696 809 697 #: includes/admin/class-filikod-media-alt-status.php:22 8810 #: includes/admin/class-filikod-media-alt-status.php:229 698 811 #: includes/dashboard/class-filikod-dashboard.php:165 699 #: includes/dashboard/class-filikod-dashboard.php:20 4812 #: includes/dashboard/class-filikod-dashboard.php:206 700 813 msgid "Invalid attachment." 701 814 msgstr "Fichier joint invalide." 702 815 703 #: includes/admin/class-filikod-media-alt-status.php:2 69704 #: includes/admin/class-filikod-media-alt-status.php:39 6816 #: includes/admin/class-filikod-media-alt-status.php:270 817 #: includes/admin/class-filikod-media-alt-status.php:397 705 818 msgid "OK" 706 819 msgstr "OK" 707 820 708 #: includes/admin/class-filikod-media-alt-status.php:29 0821 #: includes/admin/class-filikod-media-alt-status.php:291 709 822 msgid "Alt: %s" 710 823 msgstr "Alt : %s" 711 824 712 #: includes/admin/class-filikod-media-alt-status.php:33 0825 #: includes/admin/class-filikod-media-alt-status.php:331 713 826 msgid "ALT text passes all checks: present, specific, well-sized and unique." 714 827 msgstr "Le texte ALT passe tous les contrôles : présent, spécifique, de bonne longueur et unique." 715 828 716 #: includes/admin/class-filikod-media-alt-status.php:33 2829 #: includes/admin/class-filikod-media-alt-status.php:333 717 830 msgid "No ALT text defined. Add a short description, or mark the image as Decorative if it is purely visual." 718 831 msgstr "Aucun texte ALT défini. Ajoutez une courte description, ou marquez l’image comme Décorative si elle est purement visuelle." 719 832 720 #: includes/admin/class-filikod-media-alt-status.php:33 4833 #: includes/admin/class-filikod-media-alt-status.php:335 721 834 msgid "ALT text is too generic (e.g. \"logo\", \"image1\"). Replace it with a description specific to this image." 722 835 msgstr "Le texte ALT est trop générique (par ex. « logo », « image1 »). Remplacez-le par une description propre à cette image." 723 836 724 #: includes/admin/class-filikod-media-alt-status.php:33 8837 #: includes/admin/class-filikod-media-alt-status.php:339 725 838 msgid "ALT text is under %d characters after normalization, too short to describe the image meaningfully." 726 839 msgstr "Le texte ALT fait moins de %d caractères après normalisation, trop court pour décrire l’image utilement." 727 840 728 #: includes/admin/class-filikod-media-alt-status.php:34 4841 #: includes/admin/class-filikod-media-alt-status.php:345 729 842 msgid "ALT text exceeds %d characters. Long ALTs are poorly read by screen readers and dilute SEO signals. Aim for a concise description." 730 843 msgstr "Le texte ALT dépasse %d caractères. Les ALT longs sont mal lus par les lecteurs d’écran et diluent les signaux SEO. Visez une description concise." 731 844 732 #: includes/admin/class-filikod-media-alt-status.php:34 8845 #: includes/admin/class-filikod-media-alt-status.php:349 733 846 msgid "The same ALT text is used on at least one other image. Differentiate each image with a unique description." 734 847 msgstr "Le même texte ALT est utilisé sur au moins une autre image. Différenciez chaque image avec une description unique." 735 848 736 #: includes/admin/class-filikod-media-alt-status.php:35 0849 #: includes/admin/class-filikod-media-alt-status.php:351 737 850 msgid "Image marked as decorative (alt=\"\"). Excluded from the ALT score and ignored by screen readers." 738 851 msgstr "Image marquée comme décorative (alt=\"\"). Exclue du score ALT et ignorée par les lecteurs d’écran." 739 852 740 #: includes/admin/class-filikod-media-alt-status.php:40 3741 #: includes/duplicates/class-filikod-duplicates.php:2 02853 #: includes/admin/class-filikod-media-alt-status.php:404 854 #: includes/duplicates/class-filikod-duplicates.php:230 742 855 msgid "Duplicate file" 743 856 msgstr "Fichier en double" 744 857 745 #: includes/admin/class-filikod-media-alt-status.php:40 6858 #: includes/admin/class-filikod-media-alt-status.php:407 746 859 msgid "Exact copy of an image already in the library (same file fingerprint). See Filikod > Duplicates." 747 860 msgstr "Copie exacte d’une image déjà présente dans la médiathèque (même empreinte de fichier). Voir Filikod > Doublons." … … 762 875 763 876 #: includes/dashboard/class-filikod-dashboard.php:148 764 #: includes/dashboard/class-filikod-dashboard.php:23 5765 #: includes/duplicates/class-filikod-duplicates.php:1 229877 #: includes/dashboard/class-filikod-dashboard.php:237 878 #: includes/duplicates/class-filikod-duplicates.php:1415 766 879 #: includes/settings/class-filikod-settings.php:90 767 880 msgid "You do not have sufficient permissions to access this page." 768 881 msgstr "Vous n’avez pas les droits suffisants pour accéder à cette page." 769 882 770 #: includes/dashboard/class-filikod-dashboard.php:21 1883 #: includes/dashboard/class-filikod-dashboard.php:213 771 884 msgid "Audit module unavailable." 772 885 msgstr "Module d’audit indisponible." 773 886 774 #: includes/dashboard/class-filikod-dashboard.php:21 5887 #: includes/dashboard/class-filikod-dashboard.php:217 775 888 msgid "Unable to update decorative state." 776 889 msgstr "Impossible de mettre à jour l’état décoratif." 777 890 778 #: includes/duplicates/class-filikod-duplicates.php:206 779 msgid "This file exists %d time in your library:" 780 msgid_plural "This file exists %d times in your library (identical content, different names):" 781 msgstr[0] "Ce fichier existe %d fois dans votre médiathèque :" 782 msgstr[1] "Ce fichier existe %d fois dans votre médiathèque :" 783 784 #: includes/duplicates/class-filikod-duplicates.php:225 785 msgid "oldest" 786 msgstr "la plus ancienne" 787 788 #: includes/duplicates/class-filikod-duplicates.php:228 789 msgid "this file" 790 msgstr "ce fichier" 791 792 #: includes/duplicates/class-filikod-duplicates.php:236 793 msgid "Review and clean up in Filikod > Duplicates" 794 msgstr "Vérifier et nettoyer dans Filikod > Doublons" 795 796 #: includes/duplicates/class-filikod-duplicates.php:279 797 msgid "Duplicate file:" 798 msgstr "Fichier en double :" 799 800 #: includes/duplicates/class-filikod-duplicates.php:281 801 msgid "%1$s is an exact copy of %2$s (ID %3$s) already in your library." 802 msgstr "%1$s est une copie exacte de %2$s (ID %3$s) déjà présent dans votre médiathèque." 803 804 #: includes/duplicates/class-filikod-duplicates.php:282 805 msgid "Review in Filikod > Duplicates" 806 msgstr "Vérifier dans Filikod > Doublons" 807 808 #: includes/duplicates/class-filikod-duplicates.php:283 809 msgid "Dismiss this notice." 810 msgstr "Ignorer cet avertissement." 811 812 #: includes/duplicates/class-filikod-duplicates.php:908 813 #: includes/duplicates/class-filikod-duplicates.php:921 891 #: includes/duplicates/class-filikod-duplicates.php:1094 892 #: includes/duplicates/class-filikod-duplicates.php:1107 893 #: includes/duplicates/class-filikod-visual-duplicates.php:745 814 894 #: includes/settings/class-filikod-settings.php:258 815 895 #: includes/settings/class-filikod-settings.php:280 … … 819 899 msgstr "Le contrôle de sécurité a échoué." 820 900 901 #: includes/duplicates/class-filikod-duplicates.php:234 902 msgid "This file exists %d time in your library:" 903 msgid_plural "This file exists %d times in your library (identical content, different names):" 904 msgstr[0] "Ce fichier existe %d fois dans votre médiathèque :" 905 msgstr[1] "Ce fichier existe %d fois dans votre médiathèque :" 906 907 #: includes/duplicates/class-filikod-duplicates.php:253 908 msgid "oldest" 909 msgstr "la plus ancienne" 910 911 #: includes/duplicates/class-filikod-duplicates.php:256 912 msgid "this file" 913 msgstr "ce fichier" 914 915 #: includes/duplicates/class-filikod-duplicates.php:264 916 msgid "Review and clean up in Filikod > Duplicates" 917 msgstr "Vérifier et nettoyer dans Filikod > Doublons" 918 919 #: includes/duplicates/class-filikod-duplicates.php:307 920 msgid "Duplicate file:" 921 msgstr "Fichier en double :" 922 923 #: includes/duplicates/class-filikod-duplicates.php:309 924 msgid "%1$s is an exact copy of %2$s (ID %3$s) already in your library." 925 msgstr "%1$s est une copie exacte de %2$s (ID %3$s) déjà présent dans votre médiathèque." 926 927 #: includes/duplicates/class-filikod-duplicates.php:310 928 msgid "Review in Filikod > Duplicates" 929 msgstr "Vérifier dans Filikod > Doublons" 930 931 #: includes/duplicates/class-filikod-duplicates.php:311 932 msgid "Dismiss this notice." 933 msgstr "Ignorer cet avertissement." 934 821 935 #: includes/file-types/class-filikod-file-types.php:100 822 936 msgid "RAR archives" -
filikod/trunk/languages/filikod.pot
r3737001 r3763820 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Filikod 1. 2.1\n"5 "Project-Id-Version: Filikod 1.3.0\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/filikod\n" 7 "POT-Creation-Date: 2026-10- 06 12:03+0000\n"7 "POT-Creation-Date: 2026-10-24 19:43+0000\n" 8 8 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 9 9 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 16 16 "X-Domain: filikod\n" 17 17 18 #: admin/views/alt-audit.php:111 18 #: admin/views/alt-audit.php:109 19 msgid "With WPGlobus or qTranslate-XT, an image is also listed here when its ALT is empty in one of the site languages (see the Language column)." 20 msgstr "" 21 22 #: admin/views/alt-audit.php:122 23 #: includes/admin/class-filikod-admin.php:68 19 24 #: includes/admin/class-filikod-admin.php:69 20 #: includes/admin/class-filikod-admin.php:7021 25 msgid "ALT Audit" 22 26 msgstr "" 23 27 24 #: admin/views/alt-audit.php:1 1728 #: admin/views/alt-audit.php:128 25 29 msgid "Filter by issue type" 26 30 msgstr "" 27 31 28 #: admin/views/alt-audit.php:1 4732 #: admin/views/alt-audit.php:158 29 33 msgid "No images match this filter." 30 34 msgstr "" 31 35 32 #: admin/views/alt-audit.php:152 33 #: admin/views/duplicates.php:167 34 #: admin/views/duplicates.php:276 36 #: admin/views/alt-audit.php:163 37 #: admin/views/duplicates-similar.php:88 38 #: admin/views/duplicates.php:192 39 #: admin/views/duplicates.php:308 35 40 msgid "Thumbnail" 36 41 msgstr "" 37 42 38 #: admin/views/alt-audit.php:153 39 #: admin/views/duplicates.php:277 43 #: admin/views/alt-audit.php:164 44 #: admin/views/duplicates-similar.php:89 45 #: admin/views/duplicates.php:309 40 46 msgid "File name" 41 47 msgstr "" 42 48 43 #: admin/views/alt-audit.php:154 44 #: admin/views/duplicates.php:169 45 #: admin/views/duplicates.php:278 49 #: admin/views/alt-audit.php:166 50 msgid "Language" 51 msgstr "" 52 53 #: admin/views/alt-audit.php:168 54 #: admin/views/duplicates-similar.php:90 55 #: admin/views/duplicates.php:194 56 #: admin/views/duplicates.php:310 46 57 msgid "Context" 47 58 msgstr "" 48 59 49 #: admin/views/alt-audit.php:155 50 #: admin/views/duplicates.php:170 51 #: admin/views/duplicates.php:279 60 #: admin/views/alt-audit.php:169 61 #: admin/views/duplicates-similar.php:91 62 #: admin/views/duplicates.php:195 63 #: admin/views/duplicates.php:311 52 64 msgid "ALT" 53 65 msgstr "" 54 66 55 #: admin/views/alt-audit.php:156 56 #: admin/views/duplicates.php:280 67 #: admin/views/alt-audit.php:170 68 #: admin/views/duplicates-similar.php:92 69 #: admin/views/duplicates.php:312 57 70 msgid "Issue" 58 71 msgstr "" 59 72 60 #: admin/views/alt-audit.php:158 61 #: admin/views/duplicates.php:171 62 #: admin/views/duplicates.php:281 73 #: admin/views/alt-audit.php:172 74 #: admin/views/duplicates-similar.php:93 75 #: admin/views/duplicates.php:196 76 #: admin/views/duplicates.php:313 63 77 msgid "Action" 64 78 msgstr "" 65 79 66 #: admin/views/alt-audit.php:211 80 #: admin/views/alt-audit.php:231 81 msgid "empty: %s" 82 msgstr "" 83 84 #: admin/views/alt-audit.php:240 85 msgid "Not translated" 86 msgstr "" 87 88 #: admin/views/alt-audit.php:252 67 89 msgid "ALT text for this image" 68 90 msgstr "" 69 91 70 #: admin/views/alt-audit.php:2 1192 #: admin/views/alt-audit.php:252 71 93 msgid "Decorative image (alt=\"\")" 72 94 msgstr "" 73 95 74 #: admin/views/alt-audit.php:2 4096 #: admin/views/alt-audit.php:281 75 97 msgid "Mark this image as a regular content image (requires an ALT text)." 76 98 msgstr "" 77 99 78 #: admin/views/alt-audit.php:2 40100 #: admin/views/alt-audit.php:281 79 101 msgid "Mark this image as decorative (alt=\"\", excluded from the ALT score)." 80 102 msgstr "" 81 103 82 #: admin/views/alt-audit.php:2 52104 #: admin/views/alt-audit.php:293 83 105 msgid "Save" 84 106 msgstr "" 85 107 86 #: admin/views/alt-audit.php:268 87 #: admin/views/duplicates.php:224 88 #: admin/views/duplicates.php:375 108 #: admin/views/alt-audit.php:309 109 #: admin/views/duplicates-similar.php:185 110 #: admin/views/duplicates.php:249 111 #: admin/views/duplicates.php:407 89 112 msgid "Pagination" 90 113 msgstr "" 91 114 92 #: admin/views/alt-audit.php: 27393 #: admin/views/duplicates.php:2 29115 #: admin/views/alt-audit.php:314 116 #: admin/views/duplicates.php:254 94 117 msgid "Page %1$d of %2$d (%3$d items)" 95 118 msgstr "" 96 119 97 #: admin/views/alt-audit.php:282 98 #: admin/views/duplicates.php:238 99 #: admin/views/duplicates.php:389 120 #: admin/views/alt-audit.php:323 121 #: admin/views/duplicates-similar.php:199 122 #: admin/views/duplicates.php:263 123 #: admin/views/duplicates.php:421 100 124 msgid "« Previous" 101 125 msgstr "" 102 126 103 #: admin/views/alt-audit.php:285 104 #: admin/views/duplicates.php:241 105 #: admin/views/duplicates.php:392 127 #: admin/views/alt-audit.php:326 128 #: admin/views/duplicates-similar.php:202 129 #: admin/views/duplicates.php:266 130 #: admin/views/duplicates.php:424 106 131 msgid "Next »" 107 132 msgstr "" … … 109 134 #: admin/views/alt-audit.php:67 110 135 #: admin/views/dashboard.php:103 111 #: includes/admin/class-filikod-media-alt-status.php:30 2136 #: includes/admin/class-filikod-media-alt-status.php:303 112 137 msgid "Missing" 113 138 msgstr "" … … 115 140 #: admin/views/alt-audit.php:68 116 141 #: admin/views/dashboard.php:107 117 #: includes/admin/class-filikod-media-alt-status.php:30 4142 #: includes/admin/class-filikod-media-alt-status.php:305 118 143 msgid "Generic" 119 144 msgstr "" … … 121 146 #: admin/views/alt-audit.php:69 122 147 #: admin/views/dashboard.php:111 123 #: includes/admin/class-filikod-media-alt-status.php:30 6148 #: includes/admin/class-filikod-media-alt-status.php:307 124 149 msgid "Too short" 125 150 msgstr "" … … 127 152 #: admin/views/alt-audit.php:70 128 153 #: admin/views/dashboard.php:115 129 #: includes/admin/class-filikod-media-alt-status.php:30 8154 #: includes/admin/class-filikod-media-alt-status.php:309 130 155 msgid "Too long" 131 156 msgstr "" … … 133 158 #: admin/views/alt-audit.php:71 134 159 #: admin/views/dashboard.php:119 135 #: includes/admin/class-filikod-media-alt-status.php:31 0160 #: includes/admin/class-filikod-media-alt-status.php:311 136 161 msgid "Duplicated" 137 162 msgstr "" 138 163 139 164 #: admin/views/alt-audit.php:72 140 #: admin/views/alt-audit.php:1 57141 #: admin/views/alt-audit.php:2 42165 #: admin/views/alt-audit.php:171 166 #: admin/views/alt-audit.php:283 142 167 #: admin/views/dashboard.php:123 143 #: includes/admin/class-filikod-media-alt-status.php:31 2168 #: includes/admin/class-filikod-media-alt-status.php:313 144 169 msgid "Decorative" 145 170 msgstr "" … … 213 238 #: includes/dashboard/class-filikod-dashboard.php:161 214 239 #: includes/dashboard/class-filikod-dashboard.php:168 215 #: includes/dashboard/class-filikod-dashboard.php:200 216 #: includes/dashboard/class-filikod-dashboard.php:207 217 #: includes/duplicates/class-filikod-duplicates.php:905 218 #: includes/duplicates/class-filikod-duplicates.php:918 240 #: includes/dashboard/class-filikod-dashboard.php:202 241 #: includes/dashboard/class-filikod-dashboard.php:209 242 #: includes/duplicates/class-filikod-duplicates.php:1091 243 #: includes/duplicates/class-filikod-duplicates.php:1104 244 #: includes/duplicates/class-filikod-visual-duplicates.php:742 219 245 #: includes/settings/class-filikod-settings.php:253 220 246 #: includes/settings/class-filikod-settings.php:275 … … 285 311 msgstr "" 286 312 287 #: admin/views/duplicates.php:104 313 #: admin/views/duplicates-similar.php:108 314 msgid "Group %1$d: %2$d similar images" 315 msgstr "" 316 317 #: admin/views/duplicates-similar.php:114 318 msgid "Show again" 319 msgstr "" 320 321 #: admin/views/duplicates-similar.php:125 322 #: admin/views/duplicates.php:204 323 #: admin/views/duplicates.php:348 324 #: includes/duplicates/class-filikod-duplicates.php:248 325 msgid "ID %d" 326 msgstr "" 327 328 #: admin/views/duplicates-similar.php:156 329 #: admin/views/duplicates.php:229 330 #: admin/views/duplicates.php:378 331 msgid "No ALT" 332 msgstr "" 333 334 #: admin/views/duplicates-similar.php:164 335 #: admin/views/duplicates.php:233 336 #: admin/views/duplicates.php:386 337 msgid "Edit" 338 msgstr "" 339 340 #: admin/views/duplicates-similar.php:166 341 #: admin/views/duplicates.php:235 342 #: admin/views/duplicates.php:388 343 msgid "Library" 344 msgstr "" 345 346 #: admin/views/duplicates-similar.php:17 347 msgid "Same language" 348 msgstr "" 349 350 #: admin/views/duplicates-similar.php:17 351 msgid "To check" 352 msgstr "" 353 354 #: admin/views/duplicates-similar.php:18 355 msgid "Other languages" 356 msgstr "" 357 358 #: admin/views/duplicates-similar.php:19 359 #: admin/views/duplicates-similar.php:114 360 msgid "Intentional" 361 msgstr "" 362 363 #: admin/views/duplicates-similar.php:190 364 #: admin/views/duplicates.php:412 365 msgid "Page %1$d of %2$d (%3$d groups)" 366 msgstr "" 367 368 #: admin/views/duplicates-similar.php:27 369 msgid "Oldest upload" 370 msgstr "" 371 372 #: admin/views/duplicates-similar.php:28 373 msgid "Similar" 374 msgstr "" 375 376 #: admin/views/duplicates-similar.php:31 377 msgid "Uploaded first among these images." 378 msgstr "" 379 380 #: admin/views/duplicates-similar.php:32 381 msgid "Shows the same picture as the first image but is a different file (re-export, compression, new name). Check the Context column before removing it from the Media Library." 382 msgstr "" 383 384 #: admin/views/duplicates-similar.php:39 385 msgid "Look-alike images uploaded in different languages, for example a map with French labels and the same map with English labels. They are usually intentional language versions, so they stay out of the main list. Link them as translations in your multilingual plugin, or mark them as intentional." 386 msgstr "" 387 388 #: admin/views/duplicates-similar.php:41 389 msgid "Groups you marked as intentional. They stay out of the list until an image is added to or removed from the group." 390 msgstr "" 391 392 #: admin/views/duplicates-similar.php:43 393 msgid "The same photo saved as different files: re-exported, compressed or renamed before being uploaded again. Filikod compares what the images show, not the files, and only lists images of the same dimensions: a banner and its smaller version are never listed. Check where each one is used, keep that one, and remove the others from the Media Library once you are sure. Nothing is deleted automatically." 394 msgstr "" 395 396 #: admin/views/duplicates-similar.php:49 397 msgid "Your server has no GD image library, so Filikod cannot compare images. Ask your host to enable the PHP GD extension." 398 msgstr "" 399 400 #: admin/views/duplicates-similar.php:55 401 msgid "%1$s of %2$s images have a visual signature; %3$s cannot be compared (SVG, icons or missing files)." 402 msgstr "" 403 404 #: admin/views/duplicates-similar.php:81 405 msgid "No visual signature yet. Click \"Scan library\" above to compute them." 406 msgstr "" 407 408 #: admin/views/duplicates-similar.php:83 409 msgid "No similar images in this list." 410 msgstr "" 411 412 #: admin/views/duplicates.php:107 413 msgid "%1$s of %2$s images fingerprinted." 414 msgstr "" 415 416 #: admin/views/duplicates.php:115 288 417 msgid "%s images are still waiting. \"Scan library\" fingerprints them; \"Full rescan\" recomputes every fingerprint, useful after files were replaced outside WordPress." 289 418 msgstr "" 290 419 291 #: admin/views/duplicates.php:1 08420 #: admin/views/duplicates.php:119 292 421 msgid "Every image is fingerprinted. \"Full rescan\" recomputes every fingerprint, useful after files were replaced outside WordPress." 293 422 msgstr "" 294 423 295 #: admin/views/duplicates.php:1 14424 #: admin/views/duplicates.php:125 296 425 msgid "%s images have no file on disk (broken attachments) and cannot be fingerprinted." 297 426 msgstr "" 298 427 299 #: admin/views/duplicates.php:121 428 #: admin/views/duplicates.php:134 429 msgid "%s images have no visual signature yet (Similar images tab): \"Scan library\" computes them." 430 msgstr "" 431 432 #: admin/views/duplicates.php:141 300 433 msgid "Scan library" 301 434 msgstr "" 302 435 303 #: admin/views/duplicates.php:1 24436 #: admin/views/duplicates.php:144 304 437 msgid "Full rescan" 305 438 msgstr "" 306 439 307 #: admin/views/duplicates.php:1 30440 #: admin/views/duplicates.php:150 308 441 #: admin/views/settings.php:143 309 442 #: admin/views/settings.php:294 … … 311 444 msgstr "" 312 445 313 #: admin/views/duplicates.php:1 31446 #: admin/views/duplicates.php:151 314 447 #: admin/views/settings.php:144 315 448 #: admin/views/settings.php:295 … … 317 450 msgstr "" 318 451 319 #: admin/views/duplicates.php:1 46452 #: admin/views/duplicates.php:166 320 453 msgid "Duplicated files" 321 454 msgstr "" 322 455 323 #: admin/views/duplicates.php:149 456 #: admin/views/duplicates.php:169 457 msgid "Similar images" 458 msgstr "" 459 460 #: admin/views/duplicates.php:172 324 461 msgid "Missing files" 325 462 msgstr "" 326 463 327 #: admin/views/duplicates.php:1 59464 #: admin/views/duplicates.php:184 328 465 msgid "Image attachments whose file could not be found on this server: the scan could not fingerprint them. They still appear in the Media Library and may be referenced in your content. If your media is offloaded to external storage (S3, CDN), these files are simply not fingerprinted and display normally. Otherwise they render as broken images: restore the file before deleting anything from the Media Library." 329 466 msgstr "" 330 467 331 #: admin/views/duplicates.php:1 62468 #: admin/views/duplicates.php:187 332 469 msgid "No missing file among the scanned images." 333 470 msgstr "" 334 471 335 #: admin/views/duplicates.php:1 68472 #: admin/views/duplicates.php:193 336 473 msgid "Expected file" 337 474 msgstr "" 338 475 339 #: admin/views/duplicates.php:179 340 #: admin/views/duplicates.php:316 341 #: includes/duplicates/class-filikod-duplicates.php:220 342 msgid "ID %d" 343 msgstr "" 344 345 #: admin/views/duplicates.php:204 346 #: admin/views/duplicates.php:346 347 msgid "No ALT" 348 msgstr "" 349 350 #: admin/views/duplicates.php:208 351 #: admin/views/duplicates.php:354 352 msgid "Edit" 353 msgstr "" 354 355 #: admin/views/duplicates.php:210 356 #: admin/views/duplicates.php:356 357 msgid "Library" 358 msgstr "" 359 360 #: admin/views/duplicates.php:253 361 msgid "Exact copies of the same file (same md5 fingerprint) uploaded more than once. Nothing is deleted or merged: check where each copy is used, keep that one, and remove the others from the Media Library once you are sure. Media linked as translations of each other (Polylang, WPML, Bogo) are intentional and never listed here." 362 msgstr "" 363 364 #: admin/views/duplicates.php:257 476 #: admin/views/duplicates.php:279 477 msgid "Exact copies of the same file (same md5 fingerprint) uploaded more than once. Nothing is deleted or merged: check where each copy is used, keep that one, and remove the others from the Media Library once you are sure." 478 msgstr "" 479 480 #: admin/views/duplicates.php:283 481 msgid "Media linked as translations of each other (Polylang, WPML, Bogo) are intentional and never listed here." 482 msgstr "" 483 484 #: admin/views/duplicates.php:289 365 485 msgid "No scan yet. Click \"Scan library\" above to fingerprint your images." 366 486 msgstr "" 367 487 368 #: admin/views/duplicates.php:2 59488 #: admin/views/duplicates.php:291 369 489 msgid "No duplicate images found among the fingerprinted files." 370 490 msgstr "" 371 491 372 #: admin/views/duplicates.php:2 65492 #: admin/views/duplicates.php:297 373 493 msgid "%1$s groups, %2$s redundant files, %3$s that could be freed by keeping a single copy per group." 374 494 msgstr "" 375 495 376 #: admin/views/duplicates.php:3 01496 #: admin/views/duplicates.php:333 377 497 msgid "Group %1$d: %2$d identical copies, %3$s could be freed" 378 498 msgstr "" 379 499 380 #: admin/views/duplicates.php: 37500 #: admin/views/duplicates.php:48 381 501 msgid "Reference" 382 502 msgstr "" 383 503 384 #: admin/views/duplicates.php: 38385 #: includes/duplicates/class-filikod-duplicates.php:2 40504 #: admin/views/duplicates.php:49 505 #: includes/duplicates/class-filikod-duplicates.php:268 386 506 msgid "Duplicate" 387 507 msgstr "" 388 508 389 #: admin/views/duplicates.php:380 390 msgid "Page %1$d of %2$d (%3$d groups)" 391 msgstr "" 392 393 #: admin/views/duplicates.php:41 509 #: admin/views/duplicates.php:52 394 510 msgid "Oldest copy of this file: the one to keep, unless another copy is the one actually used in your content." 395 511 msgstr "" 396 512 397 #: admin/views/duplicates.php: 42513 #: admin/views/duplicates.php:53 398 514 msgid "Exact copy (same md5 fingerprint) of an image uploaded earlier. Check the Context column before removing it from the Media Library." 399 515 msgstr "" 400 516 401 #: admin/views/duplicates.php: 83402 #: admin/views/duplicates.php:1 44403 #: includes/admin/class-filikod-admin.php:8 1517 #: admin/views/duplicates.php:94 518 #: admin/views/duplicates.php:164 519 #: includes/admin/class-filikod-admin.php:80 404 520 msgid "Duplicates" 405 521 msgstr "" 406 522 407 #: admin/views/duplicates.php: 88523 #: admin/views/duplicates.php:99 408 524 msgid "Filikod fingerprints the original file of every image (md5) to find exact copies. New uploads are fingerprinted automatically; run the scan once for the images already in your library." 409 msgstr ""410 411 #: admin/views/duplicates.php:96412 msgid "%1$s of %2$s images fingerprinted."413 525 msgstr "" 414 526 … … 574 686 msgstr "" 575 687 576 #: filikod.php:2 51688 #: filikod.php:261 577 689 msgid "Filikod requires WordPress 5.8 or higher." 578 690 msgstr "" 579 691 580 #: filikod.php:2 61692 #: filikod.php:271 581 693 msgid "Filikod requires PHP 7.4 or higher." 582 694 msgstr "" 583 695 696 #: includes/admin/class-filikod-admin.php:141 697 msgid "Settings saved successfully!" 698 msgstr "" 699 584 700 #: includes/admin/class-filikod-admin.php:142 585 msgid " Settings saved successfully!"701 msgid "Error saving settings." 586 702 msgstr "" 587 703 588 704 #: includes/admin/class-filikod-admin.php:143 589 msgid " Error saving settings."705 msgid "Processing..." 590 706 msgstr "" 591 707 592 708 #: includes/admin/class-filikod-admin.php:144 593 msgid "Processing..." 709 #: includes/duplicates/class-filikod-visual-duplicates.php:750 710 msgid "An error occurred." 594 711 msgstr "" 595 712 596 713 #: includes/admin/class-filikod-admin.php:145 597 msgid "An error occurred ."714 msgid "An error occurred during processing." 598 715 msgstr "" 599 716 600 717 #: includes/admin/class-filikod-admin.php:146 601 msgid " An error occurred during processing."718 msgid "Starting processing..." 602 719 msgstr "" 603 720 604 721 #: includes/admin/class-filikod-admin.php:147 605 msgid " Starting processing..."722 msgid "images resized" 606 723 msgstr "" 607 724 608 725 #: includes/admin/class-filikod-admin.php:148 609 msgid "image sresized"726 msgid "image resized" 610 727 msgstr "" 611 728 612 729 #: includes/admin/class-filikod-admin.php:149 613 msgid "i mage resized"730 msgid "ignored" 614 731 msgstr "" 615 732 616 733 #: includes/admin/class-filikod-admin.php:150 617 msgid "ignored"618 msgstr ""619 620 #: includes/admin/class-filikod-admin.php:151621 734 msgid "saved" 622 735 msgstr "" 623 736 737 #: includes/admin/class-filikod-admin.php:152 738 msgid "Resize complete: %1$d images resized, %2$d ignored. Space saved: %3$s" 739 msgstr "" 740 624 741 #: includes/admin/class-filikod-admin.php:153 625 msgid " Resize complete: %1$d images resized, %2$d ignored. Space saved: %3$s"742 msgid "Unable to retrieve image count" 626 743 msgstr "" 627 744 628 745 #: includes/admin/class-filikod-admin.php:154 629 msgid " Unable to retrieveimage count"746 msgid "Error retrieving image count" 630 747 msgstr "" 631 748 632 749 #: includes/admin/class-filikod-admin.php:155 633 msgid " Error retrieving image count"750 msgid "image processed" 634 751 msgstr "" 635 752 636 753 #: includes/admin/class-filikod-admin.php:156 637 msgid "image processed"754 msgid "images processed" 638 755 msgstr "" 639 756 640 757 #: includes/admin/class-filikod-admin.php:157 641 msgid "images processed"642 msgstr ""643 644 758 #: includes/admin/class-filikod-admin.php:158 645 #: includes/admin/class-filikod-admin.php:159646 759 msgid "skipped" 647 760 msgstr "" 648 761 762 #: includes/admin/class-filikod-admin.php:160 763 msgid "Processing complete: %1$d images processed, %2$d skipped." 764 msgstr "" 765 649 766 #: includes/admin/class-filikod-admin.php:161 650 msgid " Processing complete: %1$d images processed, %2$d skipped."767 msgid "Saved." 651 768 msgstr "" 652 769 653 770 #: includes/admin/class-filikod-admin.php:162 654 msgid " Saved."771 msgid "Error saving ALT." 655 772 msgstr "" 656 773 657 774 #: includes/admin/class-filikod-admin.php:163 658 msgid " Error saving ALT."775 msgid "Marked as decorative (excluded from the ALT score)." 659 776 msgstr "" 660 777 661 778 #: includes/admin/class-filikod-admin.php:164 662 msgid "Marked as decorative (excluded from the ALT score)."663 msgstr ""664 665 #: includes/admin/class-filikod-admin.php:165666 779 msgid "Image restored as a regular image, please add an ALT text." 667 780 msgstr "" 668 781 669 #: includes/admin/class-filikod-admin.php:16 7782 #: includes/admin/class-filikod-admin.php:166 670 783 msgid "%1$d fingerprinted, %2$d skipped (%3$d%%)" 671 784 msgstr "" 672 785 673 #: includes/admin/class-filikod-admin.php:16 9786 #: includes/admin/class-filikod-admin.php:168 674 787 msgid "Scan complete: %1$d images fingerprinted, %2$d duplicate groups found (%3$d redundant files). Reloading..." 675 788 msgstr "" 676 789 677 #: includes/admin/class-filikod-admin.php:4 7790 #: includes/admin/class-filikod-admin.php:46 678 791 msgid "Filikod" 679 792 msgstr "" 680 793 794 #: includes/admin/class-filikod-admin.php:58 681 795 #: includes/admin/class-filikod-admin.php:59 682 #: includes/admin/class-filikod-admin.php:60683 796 msgid "Dashboard" 684 797 msgstr "" 685 798 686 #: includes/admin/class-filikod-admin.php: 80799 #: includes/admin/class-filikod-admin.php:79 687 800 msgid "Duplicate Images" 688 801 msgstr "" 689 802 803 #: includes/admin/class-filikod-admin.php:89 690 804 #: includes/admin/class-filikod-admin.php:90 691 #: includes/admin/class-filikod-admin.php:91692 805 msgid "Settings" 693 806 msgstr "" 694 807 695 #: includes/admin/class-filikod-media-alt-status.php:22 3696 #: includes/admin/class-filikod-media-alt-status.php:23 1808 #: includes/admin/class-filikod-media-alt-status.php:224 809 #: includes/admin/class-filikod-media-alt-status.php:232 697 810 msgid "Forbidden." 698 811 msgstr "" 699 812 700 #: includes/admin/class-filikod-media-alt-status.php:22 8813 #: includes/admin/class-filikod-media-alt-status.php:229 701 814 #: includes/dashboard/class-filikod-dashboard.php:165 702 #: includes/dashboard/class-filikod-dashboard.php:20 4815 #: includes/dashboard/class-filikod-dashboard.php:206 703 816 msgid "Invalid attachment." 704 817 msgstr "" 705 818 706 #: includes/admin/class-filikod-media-alt-status.php:2 69707 #: includes/admin/class-filikod-media-alt-status.php:39 6819 #: includes/admin/class-filikod-media-alt-status.php:270 820 #: includes/admin/class-filikod-media-alt-status.php:397 708 821 msgid "OK" 709 822 msgstr "" 710 823 711 #: includes/admin/class-filikod-media-alt-status.php:29 0824 #: includes/admin/class-filikod-media-alt-status.php:291 712 825 msgid "Alt: %s" 713 826 msgstr "" 714 827 715 #: includes/admin/class-filikod-media-alt-status.php:33 0828 #: includes/admin/class-filikod-media-alt-status.php:331 716 829 msgid "ALT text passes all checks: present, specific, well-sized and unique." 717 830 msgstr "" 718 831 719 #: includes/admin/class-filikod-media-alt-status.php:33 2832 #: includes/admin/class-filikod-media-alt-status.php:333 720 833 msgid "No ALT text defined. Add a short description, or mark the image as Decorative if it is purely visual." 721 834 msgstr "" 722 835 723 #: includes/admin/class-filikod-media-alt-status.php:33 4836 #: includes/admin/class-filikod-media-alt-status.php:335 724 837 msgid "ALT text is too generic (e.g. \"logo\", \"image1\"). Replace it with a description specific to this image." 725 838 msgstr "" 726 839 727 #: includes/admin/class-filikod-media-alt-status.php:33 8840 #: includes/admin/class-filikod-media-alt-status.php:339 728 841 msgid "ALT text is under %d characters after normalization, too short to describe the image meaningfully." 729 842 msgstr "" 730 843 731 #: includes/admin/class-filikod-media-alt-status.php:34 4844 #: includes/admin/class-filikod-media-alt-status.php:345 732 845 msgid "ALT text exceeds %d characters. Long ALTs are poorly read by screen readers and dilute SEO signals. Aim for a concise description." 733 846 msgstr "" 734 847 735 #: includes/admin/class-filikod-media-alt-status.php:34 8848 #: includes/admin/class-filikod-media-alt-status.php:349 736 849 msgid "The same ALT text is used on at least one other image. Differentiate each image with a unique description." 737 850 msgstr "" 738 851 739 #: includes/admin/class-filikod-media-alt-status.php:35 0852 #: includes/admin/class-filikod-media-alt-status.php:351 740 853 msgid "Image marked as decorative (alt=\"\"). Excluded from the ALT score and ignored by screen readers." 741 854 msgstr "" 742 855 743 #: includes/admin/class-filikod-media-alt-status.php:40 3744 #: includes/duplicates/class-filikod-duplicates.php:2 02856 #: includes/admin/class-filikod-media-alt-status.php:404 857 #: includes/duplicates/class-filikod-duplicates.php:230 745 858 msgid "Duplicate file" 746 859 msgstr "" 747 860 748 #: includes/admin/class-filikod-media-alt-status.php:40 6861 #: includes/admin/class-filikod-media-alt-status.php:407 749 862 msgid "Exact copy of an image already in the library (same file fingerprint). See Filikod > Duplicates." 750 863 msgstr "" … … 765 878 766 879 #: includes/dashboard/class-filikod-dashboard.php:148 767 #: includes/dashboard/class-filikod-dashboard.php:23 5768 #: includes/duplicates/class-filikod-duplicates.php:1 229880 #: includes/dashboard/class-filikod-dashboard.php:237 881 #: includes/duplicates/class-filikod-duplicates.php:1415 769 882 #: includes/settings/class-filikod-settings.php:90 770 883 msgid "You do not have sufficient permissions to access this page." 771 884 msgstr "" 772 885 773 #: includes/dashboard/class-filikod-dashboard.php:21 1886 #: includes/dashboard/class-filikod-dashboard.php:213 774 887 msgid "Audit module unavailable." 775 888 msgstr "" 776 889 777 #: includes/dashboard/class-filikod-dashboard.php:21 5890 #: includes/dashboard/class-filikod-dashboard.php:217 778 891 msgid "Unable to update decorative state." 779 892 msgstr "" 780 893 781 #: includes/duplicates/class-filikod-duplicates.php:206 782 msgid "This file exists %d time in your library:" 783 msgid_plural "This file exists %d times in your library (identical content, different names):" 784 msgstr[0] "" 785 msgstr[1] "" 786 787 #: includes/duplicates/class-filikod-duplicates.php:225 788 msgid "oldest" 789 msgstr "" 790 791 #: includes/duplicates/class-filikod-duplicates.php:228 792 msgid "this file" 793 msgstr "" 794 795 #: includes/duplicates/class-filikod-duplicates.php:236 796 msgid "Review and clean up in Filikod > Duplicates" 797 msgstr "" 798 799 #: includes/duplicates/class-filikod-duplicates.php:279 800 msgid "Duplicate file:" 801 msgstr "" 802 803 #: includes/duplicates/class-filikod-duplicates.php:281 804 msgid "%1$s is an exact copy of %2$s (ID %3$s) already in your library." 805 msgstr "" 806 807 #: includes/duplicates/class-filikod-duplicates.php:282 808 msgid "Review in Filikod > Duplicates" 809 msgstr "" 810 811 #: includes/duplicates/class-filikod-duplicates.php:283 812 msgid "Dismiss this notice." 813 msgstr "" 814 815 #: includes/duplicates/class-filikod-duplicates.php:908 816 #: includes/duplicates/class-filikod-duplicates.php:921 894 #: includes/duplicates/class-filikod-duplicates.php:1094 895 #: includes/duplicates/class-filikod-duplicates.php:1107 896 #: includes/duplicates/class-filikod-visual-duplicates.php:745 817 897 #: includes/settings/class-filikod-settings.php:258 818 898 #: includes/settings/class-filikod-settings.php:280 … … 822 902 msgstr "" 823 903 904 #: includes/duplicates/class-filikod-duplicates.php:234 905 msgid "This file exists %d time in your library:" 906 msgid_plural "This file exists %d times in your library (identical content, different names):" 907 msgstr[0] "" 908 msgstr[1] "" 909 910 #: includes/duplicates/class-filikod-duplicates.php:253 911 msgid "oldest" 912 msgstr "" 913 914 #: includes/duplicates/class-filikod-duplicates.php:256 915 msgid "this file" 916 msgstr "" 917 918 #: includes/duplicates/class-filikod-duplicates.php:264 919 msgid "Review and clean up in Filikod > Duplicates" 920 msgstr "" 921 922 #: includes/duplicates/class-filikod-duplicates.php:307 923 msgid "Duplicate file:" 924 msgstr "" 925 926 #: includes/duplicates/class-filikod-duplicates.php:309 927 msgid "%1$s is an exact copy of %2$s (ID %3$s) already in your library." 928 msgstr "" 929 930 #: includes/duplicates/class-filikod-duplicates.php:310 931 msgid "Review in Filikod > Duplicates" 932 msgstr "" 933 934 #: includes/duplicates/class-filikod-duplicates.php:311 935 msgid "Dismiss this notice." 936 msgstr "" 937 824 938 #: includes/file-types/class-filikod-file-types.php:100 825 939 msgid "RAR archives" -
filikod/trunk/readme.txt
r3737019 r3763820 6 6 Tested up to: 7.1 7 7 Requires PHP: 7.4 8 Stable tag: 1. 2.18 Stable tag: 1.3.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 38 38 39 39 * **SEO professionals** who want ALT text treated as a measurable image SEO signal, not an afterthought 40 * **Accessibility-focused teams** who need a structured audit to meet WCAG 2. 1success criterion 1.1.1 (non-text content)40 * **Accessibility-focused teams** who need a structured audit to meet WCAG 2.2 success criterion 1.1.1 (non-text content) 41 41 * **Content managers** with large media libraries who need to fix issues fast, in bulk 42 42 * **Agencies** managing multiple WordPress sites who need a repeatable quality process … … 60 60 --- 61 61 62 = Accessibility Standards: WCAG 2.2, ADA, Section 508, EAA, RGAA = 63 64 Every major accessibility standard starts with the same rule for images: WCAG success criterion 1.1.1 (Non-text Content, Level A). An image that carries information needs a text alternative. A purely decorative image needs an empty one (`alt=""`) so screen readers skip it. 65 66 That rule sits behind the laws and frameworks your clients ask about: 67 68 * **WCAG 2.2** (and 2.1, 2.0): success criterion 1.1.1, unchanged since 2008 69 * **ADA** (United States): the Title II rule for state and local governments references WCAG 2.1 AA, and WCAG is the usual benchmark in ADA website cases 70 * **Section 508** (US federal websites): WCAG 2.0 AA 71 * **European Accessibility Act (EAA)**: applies since June 28, 2025, through EN 301 549, which references WCAG 2.1 AA 72 * **RGAA** (France): theme 1 "Images", criteria 1.1 to 1.9 73 74 Filikod checks the image part of these standards across your whole media library: images with no text alternative, generic or filename-like ALT text that a screen reader reads out as noise ("IMG_4832", "photo"), and images you mark as **decorative** so they keep an intentional empty `alt=""`. WooCommerce product images live in the media library too, so they are audited like any other image. 75 76 Filikod covers this one criterion, not a whole standard. It does not replace a full accessibility audit, and no plugin can make a site compliant on its own. What it gives you is a measured starting point for one of the most frequent failures on the web. 77 78 --- 79 62 80 = Bulk ALT Management = 63 81 … … 96 114 97 115 Multilingual sites are handled. Media linked as translations of each other by **Polylang**, **WPML** (Media Translation) or **Bogo**, one attachment per language for the same file, are intentional and never reported as duplicates; unlinked copies are listed with their language so you can link them or keep one. **Weglot**, **GTranslate** and **TranslatePress** translate on the fly and never duplicate media, **WPGlobus** keeps every language in the same post, and **MultilingualPress** uses one media library per site, each scanned on its own: nothing to configure with them. 116 117 **Similar images.** The same photo is often uploaded twice as two different files: re-exported, compressed or renamed. Filikod gives every image a small visual signature, computed once on your server from a thumbnail WordPress already made (no AI, no external service), and lists look-alike images of the same dimensions in **Filikod > Duplicates > Similar images**. A banner and its smaller version are never listed. On multilingual sites, look-alikes uploaded in two different languages, such as a map with French labels and the same map in English, are language versions: they are listed apart and hidden by default. Any group can be marked as intentional. 98 118 99 119 Once the library is scanned, every copy carries a red **Duplicate file** badge in the Media Library grid and a **Duplicate** warning in the attachment details listing every copy of the group, with a link to the Duplicates page. The **Duplicate Upload Alert** (on by default, switch in Settings > Optimizations) adds a notice in the uploader the moment you upload a copy of an existing image. Nothing is blocked. … … 156 176 * Compatible with Elementor, Divi, Gutenberg, WPBakery and most major page builders 157 177 * Single site and multisite supported 158 * Multilingual: Polylang, WPML, Bogo (translation links respected by the duplicate detection), Weglot, GTranslate, TranslatePress, WPGlobus, MultilingualPress 178 * Multilingual: Polylang, WPML, Bogo (translation links respected by the duplicate detection, language column in the ALT Audit), Weglot, GTranslate, TranslatePress, MultilingualPress 179 * WPGlobus and qTranslate-XT: ALT text written in several languages is audited language by language, and an ALT left empty in one language is reported 159 180 * No external services required, runs entirely inside WordPress 160 181 … … 184 205 A checker first. Filikod audits the alt text that already exists in your media library and scores it. It never sends your images to an external AI service and never invents descriptions behind your back. An optional, local filename-based setting (off by default) can fill a starting ALT for images that have none, and you then review it in the ALT Audit like any other ALT text. 185 206 207 = Does Filikod make my site WCAG, ADA or EAA compliant? = 208 209 No plugin can do that on its own. Filikod covers the image part of these standards (WCAG success criterion 1.1.1, RGAA theme 1): it finds images without a proper text alternative, lets you fix them in bulk and keeps decorative images with an intentional empty `alt=""`. Color contrast, keyboard navigation, forms and the rest of WCAG need their own checks. 210 186 211 = Does Filikod generate ALT text automatically? = 187 212 … … 247 272 248 273 == Changelog == 274 275 = 1.3.0 = 276 * **New – Similar images (Filikod > Duplicates)**: the same photo saved as different files (re-exported, compressed or renamed before being uploaded again) is now listed in its own tab. No AI and no external service: a small visual signature is computed once on your server, from a thumbnail WordPress already made. Only images of the same dimensions with the same colours are compared, so a banner and its smaller version are never listed, and plain graphics such as logos on a solid background are only listed when they match exactly. On multilingual sites, look-alikes uploaded in different languages are listed apart and hidden by default. Any group can be marked as intentional. Click "Scan library" once to compute the signatures of the images already in your library. 277 * **New – Language column in the ALT Audit** on multilingual sites (Polylang, WPML, Bogo), to spot an ALT left in the wrong language. The column and every language label stay hidden on single-language sites. 278 * **New – WPGlobus and qTranslate-XT**: ALT text written in several languages in one field is audited language by language (generic terms of each language, length per language), and an ALT left empty in one of the site languages is reported as missing. 279 * **Fix – automatic ALT on WPML sites**: the language copies that WPML Media Translation creates on upload now get their ALT too. Before, only the uploaded image did and its copies stayed empty. 280 * **Fix – no more false "Duplicated ALT" on translated images**: the language versions of one image are recognised as one image even when only one of them was fingerprinted, so filling their ALT no longer flags the whole set. 281 * **Fix – "Remove Special Characters" keeps the language tags** of WPGlobus and qTranslate-XT and cleans the text of each language instead. 282 * **Fix – "Duplicate file" badge on every language version** of a duplicated image, whatever the language the Media Library is browsed in. 283 * **Fix – menu icon**: the Filikod icon is displayed as soon as the page loads and follows your admin colour scheme (normal, hover, current page), like the WordPress icons. 284 * **Readme**: new section on WCAG 2.2, ADA, Section 508, EAA and RGAA, and what Filikod covers. 249 285 250 286 = 1.2.1 = … … 385 421 == Upgrade Notice == 386 422 423 = 1.3.0 = 424 New Similar images tab (no AI), language column and WPGlobus / qTranslate-XT support in the ALT Audit, automatic ALT on WPML copies, fewer false duplicated ALTs. Run "Scan library" once in Filikod > Duplicates after updating. 425 387 426 = 1.2.1 = 388 427 Security and data fixes: hardened SVG sanitizer, decorative images never overwritten, ALT clean-up keeps punctuation, EXIF-safe resizing, faster audit, keyboard-accessible settings, French translation. Update recommended. -
filikod/trunk/uninstall.php
r3737001 r3763820 26 26 // Alerte anti-doublon à l'upload (module Doublons) 27 27 'filikod_duplicate_upload_alert', 28 // Groupes d'images semblables marqués « C'est voulu » (module Doublons) 29 'filikod_visual_ignored', 28 30 29 31 // Options de nettoyage des caractères spéciaux … … 41 43 // La désinstallation d'un grand réseau peut être longue : on lève la limite de temps quand c'est permis. 42 44 if (function_exists('set_time_limit')) { 43 @set_time_limit(0); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- set_time_limit may be disabled by the host.45 @set_time_limit(0); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, Squiz.PHP.DiscouragedFunctions.Discouraged -- Uninstalling a large network can take long; the host may have disabled set_time_limit. 44 46 } 45 47 … … 61 63 delete_option($filikod_option); 62 64 } 63 // Empreintes de fichiers du module Doublons (données de cache propres au plugin).65 // Empreintes de fichiers et signatures visuelles du module Doublons (données de cache propres au plugin). 64 66 delete_post_meta_by_key('_filikod_file_hash'); 67 delete_post_meta_by_key('_filikod_visual_hash'); 65 68 66 69 // Supprimer aussi les transients du sous-site ({$wpdb->options} pointe sur la table options du blog actif après switch_to_blog). … … 82 85 83 86 // Métadonnées d'images : le marqueur « décorative » (_filikod_decorative) est une décision éditoriale 84 // et reste en base ; l'empreinte md5 du module Doublons est un cache propre au plugin, sans valeur éditoriale. 87 // et reste en base ; l'empreinte md5 et la signature visuelle du module Doublons sont des caches propres 88 // au plugin, sans valeur éditoriale. 85 89 delete_post_meta_by_key('_filikod_file_hash'); 90 delete_post_meta_by_key('_filikod_visual_hash'); 86 91 87 92 // Supprimer les tâches cron
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)