---
phase: 04
plan: 07
title: Localization + Shared Dialogs + Example CSV Resources
status: pending
wave: 2
depends_on:
  - 04-02
  - 04-03
  - 04-04
  - 04-05
  - 04-06
files_modified:
  - SharepointToolbox/Localization/Strings.resx
  - SharepointToolbox/Localization/Strings.fr.resx
  - SharepointToolbox/Localization/Strings.Designer.cs
  - SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml
  - SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml.cs
  - SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml
  - SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml.cs
  - SharepointToolbox/Resources/bulk_add_members.csv
  - SharepointToolbox/Resources/bulk_create_sites.csv
  - SharepointToolbox/Resources/folder_structure.csv
  - SharepointToolbox/SharepointToolbox.csproj
autonomous: true
requirements:
  - FOLD-02

must_haves:
  truths:
    - "All Phase 4 EN/FR localization keys exist in Strings.resx and Strings.fr.resx"
    - "Strings.Designer.cs has ResourceManager accessor for new keys"
    - "ConfirmBulkOperationDialog shows operation summary and Proceed/Cancel buttons"
    - "FolderBrowserDialog shows a TreeView of SharePoint libraries and folders"
    - "Example CSV files are embedded resources accessible at runtime"
  artifacts:
    - path: "SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml"
      provides: "Pre-write confirmation dialog"
    - path: "SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml"
      provides: "Library/folder tree browser for file transfer"
    - path: "SharepointToolbox/Resources/bulk_add_members.csv"
      provides: "Example CSV for bulk member addition"
  key_links:
    - from: "ConfirmBulkOperationDialog.xaml.cs"
      to: "TranslationSource"
      via: "localized button text and labels"
      pattern: "TranslationSource.Instance"
    - from: "Strings.Designer.cs"
      to: "Strings.resx"
      via: "ResourceManager property accessor"
      pattern: "ResourceManager"
---

# Plan 04-07: Localization + Shared Dialogs + Example CSV Resources

## Goal

Add all Phase 4 EN/FR localization keys, create the ConfirmBulkOperationDialog and FolderBrowserDialog XAML dialogs, and bundle example CSV files as embedded resources. This plan creates shared infrastructure needed by all 5 tab ViewModels/Views.

## Context

Localization follows the established pattern: keys in `Strings.resx` (EN) and `Strings.fr.resx` (FR), accessor methods in `Strings.Designer.cs` (maintained manually per Phase 1 decision). UI strings use `TranslationSource.Instance[key]` in XAML.

Existing dialogs: `ProfileManagementDialog` and `SitePickerDialog` in `Views/Dialogs/`.

Example CSVs exist in `/examples/` directory. Need to copy to `Resources/` and mark as EmbeddedResource in .csproj.

## Tasks

### Task 1: Add all Phase 4 localization keys + Strings.Designer.cs update

**Files:**
- `SharepointToolbox/Localization/Strings.resx`
- `SharepointToolbox/Localization/Strings.fr.resx`
- `SharepointToolbox/Localization/Strings.Designer.cs`

**Action:**

Add the following keys to `Strings.resx` (EN values) and `Strings.fr.resx` (FR values). Do NOT remove existing keys — append only.

**New keys for Strings.resx (EN):**

```
<!-- Phase 4: Tab headers -->
tab.transfer = Transfer
tab.bulkMembers = Bulk Members
tab.bulkSites = Bulk Sites
tab.folderStructure = Folder Structure

<!-- Phase 4: Transfer tab -->
transfer.sourcesite = Source Site
transfer.destsite = Destination Site
transfer.sourcelibrary = Source Library
transfer.destlibrary = Destination Library
transfer.sourcefolder = Source Folder
transfer.destfolder = Destination Folder
transfer.mode = Transfer Mode
transfer.mode.copy = Copy
transfer.mode.move = Move
transfer.conflict = Conflict Policy
transfer.conflict.skip = Skip
transfer.conflict.overwrite = Overwrite
transfer.conflict.rename = Rename (append suffix)
transfer.browse = Browse...
transfer.start = Start Transfer
transfer.nofiles = No files found to transfer.

<!-- Phase 4: Bulk Members tab -->
bulkmembers.import = Import CSV
bulkmembers.example = Load Example
bulkmembers.execute = Add Members
bulkmembers.preview = Preview ({0} rows, {1} valid, {2} invalid)
bulkmembers.groupname = Group Name
bulkmembers.groupurl = Group URL
bulkmembers.email = Email
bulkmembers.role = Role

<!-- Phase 4: Bulk Sites tab -->
bulksites.import = Import CSV
bulksites.example = Load Example
bulksites.execute = Create Sites
bulksites.preview = Preview ({0} rows, {1} valid, {2} invalid)
bulksites.name = Name
bulksites.alias = Alias
bulksites.type = Type
bulksites.owners = Owners
bulksites.members = Members

<!-- Phase 4: Folder Structure tab -->
folderstruct.import = Import CSV
folderstruct.example = Load Example
folderstruct.execute = Create Folders
folderstruct.preview = Preview ({0} folders to create)
folderstruct.library = Target Library
folderstruct.siteurl = Site URL

<!-- Phase 4: Templates tab -->
templates.list = Saved Templates
templates.capture = Capture Template
templates.apply = Apply Template
templates.rename = Rename
templates.delete = Delete
templates.siteurl = Source Site URL
templates.name = Template Name
templates.newtitle = New Site Title
templates.newalias = New Site Alias
templates.options = Capture Options
templates.opt.libraries = Libraries
templates.opt.folders = Folders
templates.opt.permissions = Permission Groups
templates.opt.logo = Site Logo
templates.opt.settings = Site Settings
templates.empty = No templates saved yet.

<!-- Phase 4: Shared bulk operation strings -->
bulk.confirm.title = Confirm Operation
bulk.confirm.proceed = Proceed
bulk.confirm.cancel = Cancel
bulk.confirm.message = {0} — Proceed?
bulk.result.success = Completed: {0} succeeded, {1} failed
bulk.result.allfailed = All {0} items failed.
bulk.result.allsuccess = All {0} items completed successfully.
bulk.exportfailed = Export Failed Items
bulk.retryfailed = Retry Failed
bulk.validation.invalid = {0} rows have validation errors. Fix and re-import.
bulk.csvimport.title = Select CSV File
bulk.csvimport.filter = CSV Files (*.csv)|*.csv

<!-- Phase 4: Folder browser dialog -->
folderbrowser.title = Select Folder
folderbrowser.loading = Loading folder tree...
folderbrowser.select = Select
folderbrowser.cancel = Cancel
```

**New keys for Strings.fr.resx (FR):**

```
tab.transfer = Transfert
tab.bulkMembers = Ajout en masse
tab.bulkSites = Sites en masse
tab.folderStructure = Structure de dossiers

transfer.sourcesite = Site source
transfer.destsite = Site destination
transfer.sourcelibrary = Bibliotheque source
transfer.destlibrary = Bibliotheque destination
transfer.sourcefolder = Dossier source
transfer.destfolder = Dossier destination
transfer.mode = Mode de transfert
transfer.mode.copy = Copier
transfer.mode.move = Deplacer
transfer.conflict = Politique de conflit
transfer.conflict.skip = Ignorer
transfer.conflict.overwrite = Ecraser
transfer.conflict.rename = Renommer (ajouter suffixe)
transfer.browse = Parcourir...
transfer.start = Demarrer le transfert
transfer.nofiles = Aucun fichier a transferer.

bulkmembers.import = Importer CSV
bulkmembers.example = Charger l'exemple
bulkmembers.execute = Ajouter les membres
bulkmembers.preview = Apercu ({0} lignes, {1} valides, {2} invalides)
bulkmembers.groupname = Nom du groupe
bulkmembers.groupurl = URL du groupe
bulkmembers.email = Courriel
bulkmembers.role = Role

bulksites.import = Importer CSV
bulksites.example = Charger l'exemple
bulksites.execute = Creer les sites
bulksites.preview = Apercu ({0} lignes, {1} valides, {2} invalides)
bulksites.name = Nom
bulksites.alias = Alias
bulksites.type = Type
bulksites.owners = Proprietaires
bulksites.members = Membres

folderstruct.import = Importer CSV
folderstruct.example = Charger l'exemple
folderstruct.execute = Creer les dossiers
folderstruct.preview = Apercu ({0} dossiers a creer)
folderstruct.library = Bibliotheque cible
folderstruct.siteurl = URL du site

templates.list = Modeles enregistres
templates.capture = Capturer un modele
templates.apply = Appliquer le modele
templates.rename = Renommer
templates.delete = Supprimer
templates.siteurl = URL du site source
templates.name = Nom du modele
templates.newtitle = Titre du nouveau site
templates.newalias = Alias du nouveau site
templates.options = Options de capture
templates.opt.libraries = Bibliotheques
templates.opt.folders = Dossiers
templates.opt.permissions = Groupes de permissions
templates.opt.logo = Logo du site
templates.opt.settings = Parametres du site
templates.empty = Aucun modele enregistre.

bulk.confirm.title = Confirmer l'operation
bulk.confirm.proceed = Continuer
bulk.confirm.cancel = Annuler
bulk.confirm.message = {0} — Continuer ?
bulk.result.success = Termine : {0} reussis, {1} echoues
bulk.result.allfailed = Les {0} elements ont echoue.
bulk.result.allsuccess = Les {0} elements ont ete traites avec succes.
bulk.exportfailed = Exporter les elements echoues
bulk.retryfailed = Reessayer les echecs
bulk.validation.invalid = {0} lignes contiennent des erreurs. Corrigez et reimportez.
bulk.csvimport.title = Selectionner un fichier CSV
bulk.csvimport.filter = Fichiers CSV (*.csv)|*.csv

folderbrowser.title = Selectionner un dossier
folderbrowser.loading = Chargement de l'arborescence...
folderbrowser.select = Selectionner
folderbrowser.cancel = Annuler
```

Update `Strings.Designer.cs` — add ResourceManager property accessors for all new keys. Follow the exact pattern of existing entries (static property with `ResourceManager.GetString`). Since there are many keys, the executor should add all keys programmatically following the existing pattern in the file.

**Verify:**
```bash
dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -q
```

**Done:** All localization keys compile. EN and FR values present.

### Task 2: Create shared dialogs + bundle example CSVs

**Files:**
- `SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml`
- `SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml.cs`
- `SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml`
- `SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml.cs`
- `SharepointToolbox/Resources/bulk_add_members.csv`
- `SharepointToolbox/Resources/bulk_create_sites.csv`
- `SharepointToolbox/Resources/folder_structure.csv`
- `SharepointToolbox/SharepointToolbox.csproj`

**Action:**

1. Create `ConfirmBulkOperationDialog.xaml`:
```xml
<Window x:Class="SharepointToolbox.Views.Dialogs.ConfirmBulkOperationDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:loc="clr-namespace:SharepointToolbox.Localization"
        Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.title]}"
        Width="450" Height="220" WindowStartupLocation="CenterOwner"
        ResizeMode="NoResize">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TextBlock x:Name="MessageText" Grid.Row="0"
                   TextWrapping="Wrap" FontSize="14"
                   VerticalAlignment="Center" />

        <StackPanel Grid.Row="1" Orientation="Horizontal"
                    HorizontalAlignment="Right" Margin="0,20,0,0">
            <Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.cancel]}"
                    Width="100" Margin="0,0,10,0" IsCancel="True"
                    Click="Cancel_Click" />
            <Button x:Name="ProceedButton"
                    Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.proceed]}"
                    Width="100" IsDefault="True"
                    Click="Proceed_Click" />
        </StackPanel>
    </Grid>
</Window>
```

2. Create `ConfirmBulkOperationDialog.xaml.cs`:
```csharp
using System.Windows;

namespace SharepointToolbox.Views.Dialogs;

public partial class ConfirmBulkOperationDialog : Window
{
    public bool IsConfirmed { get; private set; }

    public ConfirmBulkOperationDialog(string message)
    {
        InitializeComponent();
        MessageText.Text = message;
    }

    private void Proceed_Click(object sender, RoutedEventArgs e)
    {
        IsConfirmed = true;
        DialogResult = true;
        Close();
    }

    private void Cancel_Click(object sender, RoutedEventArgs e)
    {
        IsConfirmed = false;
        DialogResult = false;
        Close();
    }
}
```

3. Create `FolderBrowserDialog.xaml`:
```xml
<Window x:Class="SharepointToolbox.Views.Dialogs.FolderBrowserDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:loc="clr-namespace:SharepointToolbox.Localization"
        Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.title]}"
        Width="400" Height="500" WindowStartupLocation="CenterOwner"
        ResizeMode="CanResizeWithGrip">
    <DockPanel Margin="10">
        <!-- Status -->
        <TextBlock x:Name="StatusText" DockPanel.Dock="Top" Margin="0,0,0,10"
                   Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.loading]}" />

        <!-- Buttons -->
        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal"
                    HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.cancel]}"
                    Width="80" Margin="0,0,10,0" IsCancel="True"
                    Click="Cancel_Click" />
            <Button x:Name="SelectButton"
                    Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.select]}"
                    Width="80" IsDefault="True" IsEnabled="False"
                    Click="Select_Click" />
        </StackPanel>

        <!-- Tree -->
        <TreeView x:Name="FolderTree" SelectedItemChanged="FolderTree_SelectedItemChanged" />
    </DockPanel>
</Window>
```

4. Create `FolderBrowserDialog.xaml.cs`:
```csharp
using System.Windows;
using System.Windows.Controls;
using Microsoft.SharePoint.Client;
using SharepointToolbox.Infrastructure.Auth;

namespace SharepointToolbox.Views.Dialogs;

public partial class FolderBrowserDialog : Window
{
    private readonly ClientContext _ctx;
    public string SelectedLibrary { get; private set; } = string.Empty;
    public string SelectedFolderPath { get; private set; } = string.Empty;

    public FolderBrowserDialog(ClientContext ctx)
    {
        InitializeComponent();
        _ctx = ctx;
        Loaded += OnLoaded;
    }

    private async void OnLoaded(object sender, RoutedEventArgs e)
    {
        try
        {
            // Load libraries
            var web = _ctx.Web;
            var lists = _ctx.LoadQuery(web.Lists
                .Include(l => l.Title, l => l.Hidden, l => l.BaseType, l => l.RootFolder)
                .Where(l => !l.Hidden && l.BaseType == BaseType.DocumentLibrary));
            var progress = new Progress<Core.Models.OperationProgress>();
            await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(_ctx, progress, CancellationToken.None);

            foreach (var list in lists)
            {
                var libNode = new TreeViewItem
                {
                    Header = list.Title,
                    Tag = new FolderNodeInfo(list.Title, string.Empty),
                };
                // Add dummy child for expand arrow
                libNode.Items.Add(new TreeViewItem { Header = "Loading..." });
                libNode.Expanded += LibNode_Expanded;
                FolderTree.Items.Add(libNode);
            }

            StatusText.Text = $"{FolderTree.Items.Count} libraries loaded.";
        }
        catch (Exception ex)
        {
            StatusText.Text = $"Error: {ex.Message}";
        }
    }

    private async void LibNode_Expanded(object sender, RoutedEventArgs e)
    {
        if (sender is not TreeViewItem node || node.Tag is not FolderNodeInfo info)
            return;

        // Only load children once
        if (node.Items.Count == 1 && node.Items[0] is TreeViewItem dummy && dummy.Header?.ToString() == "Loading...")
        {
            node.Items.Clear();
            try
            {
                var folderUrl = string.IsNullOrEmpty(info.FolderPath)
                    ? GetLibraryRootUrl(info.LibraryTitle)
                    : info.FolderPath;

                var folder = _ctx.Web.GetFolderByServerRelativeUrl(folderUrl);
                _ctx.Load(folder, f => f.Folders.Include(sf => sf.Name, sf => sf.ServerRelativeUrl));
                var progress = new Progress<Core.Models.OperationProgress>();
                await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(_ctx, progress, CancellationToken.None);

                foreach (var subFolder in folder.Folders)
                {
                    if (subFolder.Name.StartsWith("_") || subFolder.Name == "Forms")
                        continue;

                    var childNode = new TreeViewItem
                    {
                        Header = subFolder.Name,
                        Tag = new FolderNodeInfo(info.LibraryTitle, subFolder.ServerRelativeUrl),
                    };
                    childNode.Items.Add(new TreeViewItem { Header = "Loading..." });
                    childNode.Expanded += LibNode_Expanded;
                    node.Items.Add(childNode);
                }
            }
            catch (Exception ex)
            {
                node.Items.Add(new TreeViewItem { Header = $"Error: {ex.Message}" });
            }
        }
    }

    private string GetLibraryRootUrl(string libraryTitle)
    {
        var uri = new Uri(_ctx.Url);
        return $"{uri.AbsolutePath.TrimEnd('/')}/{libraryTitle}";
    }

    private void FolderTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        if (e.NewValue is TreeViewItem node && node.Tag is FolderNodeInfo info)
        {
            SelectedLibrary = info.LibraryTitle;
            SelectedFolderPath = info.FolderPath;
            SelectButton.IsEnabled = true;
        }
    }

    private void Select_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = true;
        Close();
    }

    private void Cancel_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = false;
        Close();
    }

    private record FolderNodeInfo(string LibraryTitle, string FolderPath);
}
```

5. Bundle example CSVs as embedded resources. Create `SharepointToolbox/Resources/` directory and copy the example CSVs there with extended schemas.

Create `Resources/bulk_add_members.csv`:
```
GroupName,GroupUrl,Email,Role
Marketing Team,https://contoso.sharepoint.com/sites/Marketing,user1@contoso.com,Member
Marketing Team,https://contoso.sharepoint.com/sites/Marketing,manager@contoso.com,Owner
HR Team,https://contoso.sharepoint.com/sites/HR,hr-admin@contoso.com,Owner
HR Team,https://contoso.sharepoint.com/sites/HR,recruiter@contoso.com,Member
HR Team,https://contoso.sharepoint.com/sites/HR,analyst@contoso.com,Member
IT Support,https://contoso.sharepoint.com/sites/IT,sysadmin@contoso.com,Owner
IT Support,https://contoso.sharepoint.com/sites/IT,helpdesk@contoso.com,Member
```

Create `Resources/bulk_create_sites.csv` (keep semicolon delimiter matching existing example):
```
Name;Alias;Type;Template;Owners;Members
Projet Alpha;projet-alpha;Team;;admin@contoso.com;user1@contoso.com, user2@contoso.com
Projet Beta;projet-beta;Team;;admin@contoso.com;user3@contoso.com, user4@contoso.com
Communication RH;comm-rh;Communication;;rh-admin@contoso.com;manager1@contoso.com, manager2@contoso.com
Equipe Marketing;equipe-marketing;Team;;marketing-lead@contoso.com;designer@contoso.com, redacteur@contoso.com
Portail Intranet;portail-intranet;Communication;;it-admin@contoso.com;
```

Create `Resources/folder_structure.csv` (copy from existing example):
```
Level1;Level2;Level3;Level4
Administration;;;
Administration;Comptabilite;;
Administration;Comptabilite;Factures;
Administration;Comptabilite;Bilans;
Administration;Ressources Humaines;;
Administration;Ressources Humaines;Contrats;
Administration;Ressources Humaines;Fiches de paie;
Projets;;;
Projets;Projet Alpha;;
Projets;Projet Alpha;Documents;
Projets;Projet Alpha;Livrables;
Projets;Projet Beta;;
Projets;Projet Beta;Documents;
Communication;;;
Communication;Interne;;
Communication;Interne;Notes de service;
Communication;Externe;;
Communication;Externe;Communiques de presse;
Communication;Externe;Newsletter;
```

6. Add EmbeddedResource entries to `SharepointToolbox.csproj`:
```xml
<ItemGroup>
  <EmbeddedResource Include="Resources\bulk_add_members.csv" />
  <EmbeddedResource Include="Resources\bulk_create_sites.csv" />
  <EmbeddedResource Include="Resources\folder_structure.csv" />
</ItemGroup>
```

**Verify:**
```bash
dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -q
```

**Done:** All localization keys added (EN + FR). ConfirmBulkOperationDialog and FolderBrowserDialog compile. Example CSVs bundled as embedded resources. All new XAML dialogs compile.

**Commit:** `feat(04-07): add Phase 4 localization, shared dialogs, and example CSV resources`
