All checks were successful
Release zip package / release (push) Successful in 10s
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>
577 lines
21 KiB
Markdown
577 lines
21 KiB
Markdown
---
|
|
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`
|