---
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):**
```
tab.transfer = Transfer
tab.bulkMembers = Bulk Members
tab.bulkSites = Bulk Sites
tab.folderStructure = Folder Structure
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.
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
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
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
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.
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
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
```
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
```
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();
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();
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