---
phase: 03
plan: 06
title: Localization — Phase 3 EN and FR Keys
status: pending
wave: 2
depends_on:
  - 03-01
files_modified:
  - SharepointToolbox/Localization/Strings.resx
  - SharepointToolbox/Localization/Strings.fr.resx
  - SharepointToolbox/Localization/Strings.Designer.cs
autonomous: true
requirements:
  - STOR-01
  - STOR-02
  - STOR-04
  - STOR-05
  - SRCH-01
  - SRCH-02
  - SRCH-03
  - SRCH-04
  - DUPL-01
  - DUPL-02
  - DUPL-03

must_haves:
  truths:
    - "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"
  artifacts:
    - path: "SharepointToolbox/Localization/Strings.resx"
      provides: "English localization for Phase 3 tabs"
    - path: "SharepointToolbox/Localization/Strings.fr.resx"
      provides: "French localization for Phase 3 tabs"
    - path: "SharepointToolbox/Localization/Strings.Designer.cs"
      provides: "Strongly-typed accessors for new keys"
  key_links:
    - from: "Strings.Designer.cs"
      to: "Strings.resx"
      via: "ResourceManager.GetString"
      pattern: "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`:

```xml
  <!-- 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`:

```xml
  <!-- 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 `}`):

```csharp
        // 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:**

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

Expected: 0 errors

## Verification

```bash
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`
