Files
Sharepoint-Toolbox/.planning/milestones/v1.0-phases/03-storage/03-06-PLAN.md
Dev 724fdc550d chore: complete v1.0 milestone
Archive 5 phases (36 plans) to milestones/v1.0-phases/.
Archive roadmap, requirements, and audit to milestones/.
Evolve PROJECT.md with shipped state and validated requirements.
Collapse ROADMAP.md to one-line milestone summary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:19:03 +02:00

22 KiB

phase, plan, title, status, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan title status wave depends_on files_modified autonomous requirements must_haves
03 06 Localization — Phase 3 EN and FR Keys pending 2
03-01
SharepointToolbox/Localization/Strings.resx
SharepointToolbox/Localization/Strings.fr.resx
SharepointToolbox/Localization/Strings.Designer.cs
true
STOR-01
STOR-02
STOR-04
STOR-05
SRCH-01
SRCH-02
SRCH-03
SRCH-04
DUPL-01
DUPL-02
DUPL-03
truths artifacts key_links
All Phase 3 EN keys exist in Strings.resx
All Phase 3 FR keys exist in Strings.fr.resx with non-empty French values
Strings.Designer.cs has one static property per new key (dot-to-underscore naming: chk.per.lib -> chk_per_lib)
dotnet build produces 0 errors after localization changes
No existing Phase 2 or Phase 1 keys are modified or removed
path provides
SharepointToolbox/Localization/Strings.resx English localization for Phase 3 tabs
path provides
SharepointToolbox/Localization/Strings.fr.resx French localization for Phase 3 tabs
path provides
SharepointToolbox/Localization/Strings.Designer.cs Strongly-typed accessors for new keys
from to via pattern
Strings.Designer.cs Strings.resx ResourceManager.GetString ResourceManager.GetString

Plan 03-06: Localization — Phase 3 EN and FR Keys

Goal

Add all EN and FR localization keys needed by the Storage, File Search, and Duplicates tabs. Views in plans 03-07 and 03-08 reference these keys via TranslationSource.Instance["key"] XAML bindings. Keys must exist before the Views compile.

Context

Strings.resx uses a manually maintained Strings.Designer.cs (no ResXFileCodeGenerator — confirmed in Phase 1 decisions). The naming convention converts dots to underscores: key chk.per.lib becomes accessor Strings.chk_per_lib. Both .resx files use xml:space="preserve" on each <data> element. The following keys already exist and must NOT be duplicated: tab.storage, tab.search, tab.duplicates, lbl.folder.depth, chk.max.depth.

Pre-existing keys — do not add: The following keys are confirmed present in Strings.resx from Phase 2 and must be skipped when editing both .resx files and Strings.Designer.cs:

  • grp.scan.opts (value: "Scan Options") — already exists
  • grp.export.fmt (value: "Export Format") — already exists
  • btn.cancel (value: "Cancel") — already exists

Before appending, verify with: grep -n "grp.scan.opts\|grp.export.fmt\|btn.cancel" SharepointToolbox/Localization/Strings.resx Do not add designer properties for these keys if they already exist in Strings.Designer.cs.

Tasks

Task 1: Add Phase 3 keys to Strings.resx, Strings.fr.resx, and Strings.Designer.cs

Files:

  • SharepointToolbox/Localization/Strings.resx
  • SharepointToolbox/Localization/Strings.fr.resx
  • SharepointToolbox/Localization/Strings.Designer.cs

Action: Modify — append new <data> elements before </root> in both .resx files; append new properties before the closing } in Strings.Designer.cs

Why: Views in plans 03-07 and 03-08 bind to these keys. Missing keys produce empty strings at runtime.

Add these entries immediately before the closing </root> tag in Strings.resx:

  <!-- Phase 3: Storage Tab -->
  <data name="chk.per.lib" xml:space="preserve"><value>Per-Library Breakdown</value></data>
  <data name="chk.subsites" xml:space="preserve"><value>Include Subsites</value></data>
  <data name="stor.note" xml:space="preserve"><value>Note: deeper folder scans on large sites may take several minutes.</value></data>
  <data name="btn.gen.storage" xml:space="preserve"><value>Generate Metrics</value></data>
  <data name="btn.open.storage" xml:space="preserve"><value>Open Report</value></data>
  <data name="stor.col.library" xml:space="preserve"><value>Library</value></data>
  <data name="stor.col.site" xml:space="preserve"><value>Site</value></data>
  <data name="stor.col.files" xml:space="preserve"><value>Files</value></data>
  <data name="stor.col.size" xml:space="preserve"><value>Total Size</value></data>
  <data name="stor.col.versions" xml:space="preserve"><value>Version Size</value></data>
  <data name="stor.col.lastmod" xml:space="preserve"><value>Last Modified</value></data>
  <data name="stor.col.share" xml:space="preserve"><value>Share of Total</value></data>
  <data name="stor.rad.csv" xml:space="preserve"><value>CSV</value></data>
  <data name="stor.rad.html" xml:space="preserve"><value>HTML</value></data>
  <!-- Phase 3: File Search Tab -->
  <data name="grp.search.filters" xml:space="preserve"><value>Search Filters</value></data>
  <data name="lbl.extensions" xml:space="preserve"><value>Extension(s):</value></data>
  <data name="ph.extensions" xml:space="preserve"><value>docx pdf xlsx</value></data>
  <data name="lbl.regex" xml:space="preserve"><value>Name / Regex:</value></data>
  <data name="ph.regex" xml:space="preserve"><value>Ex: report.* or \.bak$</value></data>
  <data name="chk.created.after" xml:space="preserve"><value>Created after:</value></data>
  <data name="chk.created.before" xml:space="preserve"><value>Created before:</value></data>
  <data name="chk.modified.after" xml:space="preserve"><value>Modified after:</value></data>
  <data name="chk.modified.before" xml:space="preserve"><value>Modified before:</value></data>
  <data name="lbl.created.by" xml:space="preserve"><value>Created by:</value></data>
  <data name="ph.created.by" xml:space="preserve"><value>First Last or email</value></data>
  <data name="lbl.modified.by" xml:space="preserve"><value>Modified by:</value></data>
  <data name="ph.modified.by" xml:space="preserve"><value>First Last or email</value></data>
  <data name="lbl.library" xml:space="preserve"><value>Library:</value></data>
  <data name="ph.library" xml:space="preserve"><value>Optional relative path e.g. Shared Documents</value></data>
  <data name="lbl.max.results" xml:space="preserve"><value>Max results:</value></data>
  <data name="lbl.site.url" xml:space="preserve"><value>Site URL:</value></data>
  <data name="ph.site.url" xml:space="preserve"><value>https://tenant.sharepoint.com/sites/MySite</value></data>
  <data name="btn.run.search" xml:space="preserve"><value>Run Search</value></data>
  <data name="btn.open.search" xml:space="preserve"><value>Open Results</value></data>
  <data name="srch.col.name" xml:space="preserve"><value>File Name</value></data>
  <data name="srch.col.ext" xml:space="preserve"><value>Extension</value></data>
  <data name="srch.col.created" xml:space="preserve"><value>Created</value></data>
  <data name="srch.col.modified" xml:space="preserve"><value>Modified</value></data>
  <data name="srch.col.author" xml:space="preserve"><value>Created By</value></data>
  <data name="srch.col.modby" xml:space="preserve"><value>Modified By</value></data>
  <data name="srch.col.size" xml:space="preserve"><value>Size</value></data>
  <data name="srch.col.path" xml:space="preserve"><value>Path</value></data>
  <data name="srch.rad.csv" xml:space="preserve"><value>CSV</value></data>
  <data name="srch.rad.html" xml:space="preserve"><value>HTML</value></data>
  <!-- Phase 3: Duplicates Tab -->
  <data name="grp.dup.type" xml:space="preserve"><value>Duplicate Type</value></data>
  <data name="rad.dup.files" xml:space="preserve"><value>Duplicate files</value></data>
  <data name="rad.dup.folders" xml:space="preserve"><value>Duplicate folders</value></data>
  <data name="grp.dup.criteria" xml:space="preserve"><value>Comparison Criteria</value></data>
  <data name="lbl.dup.note" xml:space="preserve"><value>Name is always the primary criterion. Check additional criteria:</value></data>
  <data name="chk.dup.size" xml:space="preserve"><value>Same size</value></data>
  <data name="chk.dup.created" xml:space="preserve"><value>Same creation date</value></data>
  <data name="chk.dup.modified" xml:space="preserve"><value>Same modification date</value></data>
  <data name="chk.dup.subfolders" xml:space="preserve"><value>Same subfolder count</value></data>
  <data name="chk.dup.filecount" xml:space="preserve"><value>Same file count</value></data>
  <data name="chk.include.subsites" xml:space="preserve"><value>Include subsites</value></data>
  <data name="ph.dup.lib" xml:space="preserve"><value>All (leave empty)</value></data>
  <data name="btn.run.scan" xml:space="preserve"><value>Run Scan</value></data>
  <data name="btn.open.results" xml:space="preserve"><value>Open Results</value></data>

Add these entries immediately before the closing </root> tag in Strings.fr.resx:

  <!-- Phase 3: Storage Tab -->
  <data name="chk.per.lib" xml:space="preserve"><value>Détail par bibliothèque</value></data>
  <data name="chk.subsites" xml:space="preserve"><value>Inclure les sous-sites</value></data>
  <data name="stor.note" xml:space="preserve"><value>Remarque : les analyses de dossiers profondes sur les grands sites peuvent prendre plusieurs minutes.</value></data>
  <data name="btn.gen.storage" xml:space="preserve"><value>Générer les métriques</value></data>
  <data name="btn.open.storage" xml:space="preserve"><value>Ouvrir le rapport</value></data>
  <data name="stor.col.library" xml:space="preserve"><value>Bibliothèque</value></data>
  <data name="stor.col.site" xml:space="preserve"><value>Site</value></data>
  <data name="stor.col.files" xml:space="preserve"><value>Fichiers</value></data>
  <data name="stor.col.size" xml:space="preserve"><value>Taille totale</value></data>
  <data name="stor.col.versions" xml:space="preserve"><value>Taille des versions</value></data>
  <data name="stor.col.lastmod" xml:space="preserve"><value>Dernière modification</value></data>
  <data name="stor.col.share" xml:space="preserve"><value>Part du total</value></data>
  <data name="stor.rad.csv" xml:space="preserve"><value>CSV</value></data>
  <data name="stor.rad.html" xml:space="preserve"><value>HTML</value></data>
  <!-- Phase 3: File Search Tab -->
  <data name="grp.search.filters" xml:space="preserve"><value>Filtres de recherche</value></data>
  <data name="lbl.extensions" xml:space="preserve"><value>Extension(s) :</value></data>
  <data name="ph.extensions" xml:space="preserve"><value>docx pdf xlsx</value></data>
  <data name="lbl.regex" xml:space="preserve"><value>Nom / Regex :</value></data>
  <data name="ph.regex" xml:space="preserve"><value>Ex : rapport.* ou \.bak$</value></data>
  <data name="chk.created.after" xml:space="preserve"><value>Créé après :</value></data>
  <data name="chk.created.before" xml:space="preserve"><value>Créé avant :</value></data>
  <data name="chk.modified.after" xml:space="preserve"><value>Modifié après :</value></data>
  <data name="chk.modified.before" xml:space="preserve"><value>Modifié avant :</value></data>
  <data name="lbl.created.by" xml:space="preserve"><value>Créé par :</value></data>
  <data name="ph.created.by" xml:space="preserve"><value>Prénom Nom ou courriel</value></data>
  <data name="lbl.modified.by" xml:space="preserve"><value>Modifié par :</value></data>
  <data name="ph.modified.by" xml:space="preserve"><value>Prénom Nom ou courriel</value></data>
  <data name="lbl.library" xml:space="preserve"><value>Bibliothèque :</value></data>
  <data name="ph.library" xml:space="preserve"><value>Chemin relatif optionnel, ex. Documents partagés</value></data>
  <data name="lbl.max.results" xml:space="preserve"><value>Max résultats :</value></data>
  <data name="lbl.site.url" xml:space="preserve"><value>URL du site :</value></data>
  <data name="ph.site.url" xml:space="preserve"><value>https://tenant.sharepoint.com/sites/MonSite</value></data>
  <data name="btn.run.search" xml:space="preserve"><value>Lancer la recherche</value></data>
  <data name="btn.open.search" xml:space="preserve"><value>Ouvrir les résultats</value></data>
  <data name="srch.col.name" xml:space="preserve"><value>Nom du fichier</value></data>
  <data name="srch.col.ext" xml:space="preserve"><value>Extension</value></data>
  <data name="srch.col.created" xml:space="preserve"><value>Créé</value></data>
  <data name="srch.col.modified" xml:space="preserve"><value>Modifié</value></data>
  <data name="srch.col.author" xml:space="preserve"><value>Créé par</value></data>
  <data name="srch.col.modby" xml:space="preserve"><value>Modifié par</value></data>
  <data name="srch.col.size" xml:space="preserve"><value>Taille</value></data>
  <data name="srch.col.path" xml:space="preserve"><value>Chemin</value></data>
  <data name="srch.rad.csv" xml:space="preserve"><value>CSV</value></data>
  <data name="srch.rad.html" xml:space="preserve"><value>HTML</value></data>
  <!-- Phase 3: Duplicates Tab -->
  <data name="grp.dup.type" xml:space="preserve"><value>Type de doublon</value></data>
  <data name="rad.dup.files" xml:space="preserve"><value>Fichiers en doublon</value></data>
  <data name="rad.dup.folders" xml:space="preserve"><value>Dossiers en doublon</value></data>
  <data name="grp.dup.criteria" xml:space="preserve"><value>Critères de comparaison</value></data>
  <data name="lbl.dup.note" xml:space="preserve"><value>Le nom est toujours le critère principal. Cochez des critères supplémentaires :</value></data>
  <data name="chk.dup.size" xml:space="preserve"><value>Même taille</value></data>
  <data name="chk.dup.created" xml:space="preserve"><value>Même date de création</value></data>
  <data name="chk.dup.modified" xml:space="preserve"><value>Même date de modification</value></data>
  <data name="chk.dup.subfolders" xml:space="preserve"><value>Même nombre de sous-dossiers</value></data>
  <data name="chk.dup.filecount" xml:space="preserve"><value>Même nombre de fichiers</value></data>
  <data name="chk.include.subsites" xml:space="preserve"><value>Inclure les sous-sites</value></data>
  <data name="ph.dup.lib" xml:space="preserve"><value>Tous (laisser vide)</value></data>
  <data name="btn.run.scan" xml:space="preserve"><value>Lancer l'analyse</value></data>
  <data name="btn.open.results" xml:space="preserve"><value>Ouvrir les résultats</value></data>

Add these properties inside the Strings class in Strings.Designer.cs (before the closing }):

        // Phase 3: Storage Tab
        public static string chk_per_lib => ResourceManager.GetString("chk.per.lib", resourceCulture) ?? string.Empty;
        public static string chk_subsites => ResourceManager.GetString("chk.subsites", resourceCulture) ?? string.Empty;
        public static string stor_note => ResourceManager.GetString("stor.note", resourceCulture) ?? string.Empty;
        public static string btn_gen_storage => ResourceManager.GetString("btn.gen.storage", resourceCulture) ?? string.Empty;
        public static string btn_open_storage => ResourceManager.GetString("btn.open.storage", resourceCulture) ?? string.Empty;
        public static string stor_col_library => ResourceManager.GetString("stor.col.library", resourceCulture) ?? string.Empty;
        public static string stor_col_site => ResourceManager.GetString("stor.col.site", resourceCulture) ?? string.Empty;
        public static string stor_col_files => ResourceManager.GetString("stor.col.files", resourceCulture) ?? string.Empty;
        public static string stor_col_size => ResourceManager.GetString("stor.col.size", resourceCulture) ?? string.Empty;
        public static string stor_col_versions => ResourceManager.GetString("stor.col.versions", resourceCulture) ?? string.Empty;
        public static string stor_col_lastmod => ResourceManager.GetString("stor.col.lastmod", resourceCulture) ?? string.Empty;
        public static string stor_col_share => ResourceManager.GetString("stor.col.share", resourceCulture) ?? string.Empty;
        public static string stor_rad_csv => ResourceManager.GetString("stor.rad.csv", resourceCulture) ?? string.Empty;
        public static string stor_rad_html => ResourceManager.GetString("stor.rad.html", resourceCulture) ?? string.Empty;

        // Phase 3: File Search Tab
        public static string grp_search_filters => ResourceManager.GetString("grp.search.filters", resourceCulture) ?? string.Empty;
        public static string lbl_extensions => ResourceManager.GetString("lbl.extensions", resourceCulture) ?? string.Empty;
        public static string ph_extensions => ResourceManager.GetString("ph.extensions", resourceCulture) ?? string.Empty;
        public static string lbl_regex => ResourceManager.GetString("lbl.regex", resourceCulture) ?? string.Empty;
        public static string ph_regex => ResourceManager.GetString("ph.regex", resourceCulture) ?? string.Empty;
        public static string chk_created_after => ResourceManager.GetString("chk.created.after", resourceCulture) ?? string.Empty;
        public static string chk_created_before => ResourceManager.GetString("chk.created.before", resourceCulture) ?? string.Empty;
        public static string chk_modified_after => ResourceManager.GetString("chk.modified.after", resourceCulture) ?? string.Empty;
        public static string chk_modified_before => ResourceManager.GetString("chk.modified.before", resourceCulture) ?? string.Empty;
        public static string lbl_created_by => ResourceManager.GetString("lbl.created.by", resourceCulture) ?? string.Empty;
        public static string ph_created_by => ResourceManager.GetString("ph.created.by", resourceCulture) ?? string.Empty;
        public static string lbl_modified_by => ResourceManager.GetString("lbl.modified.by", resourceCulture) ?? string.Empty;
        public static string ph_modified_by => ResourceManager.GetString("ph.modified.by", resourceCulture) ?? string.Empty;
        public static string lbl_library => ResourceManager.GetString("lbl.library", resourceCulture) ?? string.Empty;
        public static string ph_library => ResourceManager.GetString("ph.library", resourceCulture) ?? string.Empty;
        public static string lbl_max_results => ResourceManager.GetString("lbl.max.results", resourceCulture) ?? string.Empty;
        public static string lbl_site_url => ResourceManager.GetString("lbl.site.url", resourceCulture) ?? string.Empty;
        public static string ph_site_url => ResourceManager.GetString("ph.site.url", resourceCulture) ?? string.Empty;
        public static string btn_run_search => ResourceManager.GetString("btn.run.search", resourceCulture) ?? string.Empty;
        public static string btn_open_search => ResourceManager.GetString("btn.open.search", resourceCulture) ?? string.Empty;
        public static string srch_col_name => ResourceManager.GetString("srch.col.name", resourceCulture) ?? string.Empty;
        public static string srch_col_ext => ResourceManager.GetString("srch.col.ext", resourceCulture) ?? string.Empty;
        public static string srch_col_created => ResourceManager.GetString("srch.col.created", resourceCulture) ?? string.Empty;
        public static string srch_col_modified => ResourceManager.GetString("srch.col.modified", resourceCulture) ?? string.Empty;
        public static string srch_col_author => ResourceManager.GetString("srch.col.author", resourceCulture) ?? string.Empty;
        public static string srch_col_modby => ResourceManager.GetString("srch.col.modby", resourceCulture) ?? string.Empty;
        public static string srch_col_size => ResourceManager.GetString("srch.col.size", resourceCulture) ?? string.Empty;
        public static string srch_col_path => ResourceManager.GetString("srch.col.path", resourceCulture) ?? string.Empty;
        public static string srch_rad_csv => ResourceManager.GetString("srch.rad.csv", resourceCulture) ?? string.Empty;
        public static string srch_rad_html => ResourceManager.GetString("srch.rad.html", resourceCulture) ?? string.Empty;

        // Phase 3: Duplicates Tab
        public static string grp_dup_type => ResourceManager.GetString("grp.dup.type", resourceCulture) ?? string.Empty;
        public static string rad_dup_files => ResourceManager.GetString("rad.dup.files", resourceCulture) ?? string.Empty;
        public static string rad_dup_folders => ResourceManager.GetString("rad.dup.folders", resourceCulture) ?? string.Empty;
        public static string grp_dup_criteria => ResourceManager.GetString("grp.dup.criteria", resourceCulture) ?? string.Empty;
        public static string lbl_dup_note => ResourceManager.GetString("lbl.dup.note", resourceCulture) ?? string.Empty;
        public static string chk_dup_size => ResourceManager.GetString("chk.dup.size", resourceCulture) ?? string.Empty;
        public static string chk_dup_created => ResourceManager.GetString("chk.dup.created", resourceCulture) ?? string.Empty;
        public static string chk_dup_modified => ResourceManager.GetString("chk.dup.modified", resourceCulture) ?? string.Empty;
        public static string chk_dup_subfolders => ResourceManager.GetString("chk.dup.subfolders", resourceCulture) ?? string.Empty;
        public static string chk_dup_filecount => ResourceManager.GetString("chk.dup.filecount", resourceCulture) ?? string.Empty;
        public static string chk_include_subsites => ResourceManager.GetString("chk.include.subsites", resourceCulture) ?? string.Empty;
        public static string ph_dup_lib => ResourceManager.GetString("ph.dup.lib", resourceCulture) ?? string.Empty;
        public static string btn_run_scan => ResourceManager.GetString("btn.run.scan", resourceCulture) ?? string.Empty;
        public static string btn_open_results => ResourceManager.GetString("btn.open.results", resourceCulture) ?? string.Empty;

Verification:

dotnet build C:/Users/dev/Documents/projets/Sharepoint/SharepointToolbox.slnx

Expected: 0 errors

Verification

dotnet build C:/Users/dev/Documents/projets/Sharepoint/SharepointToolbox.slnx
dotnet test C:/Users/dev/Documents/projets/Sharepoint/SharepointToolbox.Tests/SharepointToolbox.Tests.csproj -x 2>&1 | tail -5

Expected: 0 build errors; all previously passing tests still pass; no new failures

Commit Message

feat(03-06): add Phase 3 EN/FR localization keys for Storage, Search, and Duplicates tabs

Output

After completion, create .planning/phases/03-storage/03-06-SUMMARY.md