Files
Sharepoint-Toolbox/.planning/milestones/v1.0-phases/01-foundation/01-05-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

254 lines
11 KiB
Markdown

---
phase: 01-foundation
plan: 05
type: execute
wave: 3
depends_on:
- 01-02
files_modified:
- SharepointToolbox/Localization/TranslationSource.cs
- SharepointToolbox/Localization/Strings.resx
- SharepointToolbox/Localization/Strings.fr.resx
- SharepointToolbox.Tests/Localization/TranslationSourceTests.cs
- SharepointToolbox.Tests/Integration/LoggingIntegrationTests.cs
autonomous: true
requirements:
- FOUND-08
- FOUND-09
must_haves:
truths:
- "TranslationSource.Instance[key] returns the EN string for English culture"
- "Setting TranslationSource.Instance.CurrentCulture to 'fr' changes string lookup without app restart"
- "PropertyChanged fires with empty string key (signals all properties changed) on culture switch"
- "Serilog writes to rolling daily log file at %AppData%\\SharepointToolbox\\logs\\app-{date}.log"
- "Serilog ILogger<T> is injectable via DI — does not use static Log.Logger directly in services"
- "LoggingIntegrationTests verify a log file is created and contains the written message"
artifacts:
- path: "SharepointToolbox/Localization/TranslationSource.cs"
provides: "Singleton INotifyPropertyChanged string lookup for runtime culture switching"
contains: "PropertyChangedEventArgs(string.Empty)"
- path: "SharepointToolbox/Localization/Strings.resx"
provides: "EN default resource file with all Phase 1 UI strings"
- path: "SharepointToolbox/Localization/Strings.fr.resx"
provides: "FR overlay — all keys present, values stubbed with EN text"
key_links:
- from: "SharepointToolbox/Localization/TranslationSource.cs"
to: "SharepointToolbox/Localization/Strings.resx"
via: "ResourceManager from Strings class"
pattern: "Strings.ResourceManager"
- from: "MainWindow.xaml (plan 01-06)"
to: "SharepointToolbox/Localization/TranslationSource.cs"
via: "XAML binding: Source={x:Static loc:TranslationSource.Instance}, Path=[key]"
pattern: "TranslationSource.Instance"
---
<objective>
Build the logging infrastructure and dynamic localization system. Serilog wired into Generic Host. TranslationSource singleton enabling runtime culture switching without restart.
Purpose: Every feature phase needs ILogger<T> injection and localizable strings. The TranslationSource pattern (INotifyPropertyChanged indexer binding) is the only approach that refreshes WPF bindings at runtime — standard x:Static resx bindings are evaluated once at startup.
Output: TranslationSource + EN/FR resx files + Serilog integration + unit/integration tests.
</objective>
<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
@.planning/phases/01-foundation/01-02-SUMMARY.md
<interfaces>
<!-- From Core/Messages/LanguageChangedMessage.cs (plan 01-02) -->
```csharp
public sealed class LanguageChangedMessage : ValueChangedMessage<string>
{
public LanguageChangedMessage(string cultureCode) : base(cultureCode) { }
}
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: TranslationSource singleton + EN/FR resx files</name>
<files>
SharepointToolbox/Localization/TranslationSource.cs,
SharepointToolbox/Localization/Strings.resx,
SharepointToolbox/Localization/Strings.fr.resx,
SharepointToolbox.Tests/Localization/TranslationSourceTests.cs
</files>
<behavior>
- Test: TranslationSource.Instance["app.title"] returns "SharePoint Toolbox" (EN default)
- Test: After setting CurrentCulture to fr-FR, TranslationSource.Instance["app.title"] returns FR value (or EN fallback if FR not defined)
- Test: Changing CurrentCulture fires PropertyChanged with EventArgs having empty string PropertyName
- Test: Setting same culture twice does NOT fire PropertyChanged (equality check)
- Test: Missing key returns "[key]" not null (prevents NullReferenceException in bindings)
- Test: TranslationSource.Instance is same instance on multiple accesses (singleton)
</behavior>
<action>
Create `Localization/` directory.
**TranslationSource.cs** — implement exactly as per research Pattern 4:
```csharp
namespace SharepointToolbox.Localization;
public class TranslationSource : INotifyPropertyChanged
{
public static readonly TranslationSource Instance = new();
private ResourceManager _resourceManager = Strings.ResourceManager;
private CultureInfo _currentCulture = CultureInfo.CurrentUICulture;
public string this[string key] =>
_resourceManager.GetString(key, _currentCulture) ?? $"[{key}]";
public CultureInfo CurrentCulture
{
get => _currentCulture;
set
{
if (Equals(_currentCulture, value)) return;
_currentCulture = value;
Thread.CurrentThread.CurrentUICulture = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
```
**Strings.resx** — Create with ResXResourceWriter or manually as XML. Include ALL Phase 1 UI strings. Key naming mirrors existing PowerShell convention (see CONTEXT.md).
Required keys (minimum set for Phase 1 — add more as needed during shell implementation):
```
app.title = SharePoint Toolbox
toolbar.connect = Connect
toolbar.manage = Manage Profiles...
toolbar.clear = Clear Session
tab.permissions = Permissions
tab.storage = Storage
tab.search = File Search
tab.duplicates = Duplicates
tab.templates = Templates
tab.bulk = Bulk Operations
tab.structure = Folder Structure
tab.settings = Settings
tab.comingsoon = Coming soon
btn.cancel = Cancel
settings.language = Language
settings.lang.en = English
settings.lang.fr = French
settings.folder = Data output folder
settings.browse = Browse...
profile.name = Profile name
profile.url = Tenant URL
profile.clientid = Client ID
profile.add = Add
profile.rename = Rename
profile.delete = Delete
status.ready = Ready
status.cancelled = Operation cancelled
err.auth.failed = Authentication failed. Check tenant URL and Client ID.
err.generic = An error occurred. See log for details.
```
**Strings.fr.resx** — All same keys, values stubbed with EN text. A comment `<!-- FR stub — Phase 5 -->` on each value is acceptable. FR completeness is Phase 5.
**TranslationSourceTests.cs** — Replace stub with real tests.
All tests in `[Trait("Category", "Unit")]`.
TranslationSource.Instance is a static singleton — reset culture to EN in test teardown to avoid test pollution.
</action>
<verify>
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~TranslationSourceTests" 2>&1 | tail -10</automated>
</verify>
<done>TranslationSourceTests pass. Missing key returns "[key]". Culture switch fires PropertyChanged with empty property name. Strings.resx contains all required keys.</done>
</task>
<task type="auto">
<name>Task 2: Serilog integration tests and App.xaml.cs logging wiring verification</name>
<files>
SharepointToolbox.Tests/Integration/LoggingIntegrationTests.cs
</files>
<action>
The Serilog file sink is already wired in App.xaml.cs (plan 01-01). This task writes an integration test to verify the wiring produces an actual log file and that the LogPanelSink (from plan 01-02) can be instantiated without errors.
**LoggingIntegrationTests.cs** — Replace stub:
```csharp
[Trait("Category", "Integration")]
public class LoggingIntegrationTests : IDisposable
{
private readonly string _tempLogDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
[Fact]
public async Task Serilog_WritesLogFile_WhenMessageLogged()
{
Directory.CreateDirectory(_tempLogDir);
var logFile = Path.Combine(_tempLogDir, "test-.log");
var logger = new LoggerConfiguration()
.WriteTo.File(logFile, rollingInterval: RollingInterval.Day)
.CreateLogger();
logger.Information("Test log message {Value}", 42);
await logger.DisposeAsync();
var files = Directory.GetFiles(_tempLogDir, "*.log");
Assert.Single(files);
var content = await File.ReadAllTextAsync(files[0]);
Assert.Contains("Test log message 42", content);
}
[Fact]
public void LogPanelSink_CanBeInstantiated_WithRichTextBox()
{
// Verify the sink type instantiates without throwing
// Cannot test actual UI writes without STA thread — this is structural smoke only
var sinkType = typeof(LogPanelSink);
Assert.NotNull(sinkType);
Assert.True(typeof(ILogEventSink).IsAssignableFrom(sinkType));
}
public void Dispose()
{
if (Directory.Exists(_tempLogDir))
Directory.Delete(_tempLogDir, recursive: true);
}
}
```
Note: `LogPanelSink` instantiation test avoids creating a real `RichTextBox` (requires STA thread). It only verifies the type implements `ILogEventSink`. Full UI-thread integration is verified in the manual checkpoint (plan 01-08).
Also update `App.xaml.cs` RegisterServices to add `LogPanelSink` registration comment for plan 01-06:
```csharp
// LogPanelSink registered in plan 01-06 after MainWindow is created
// (requires RichTextBox reference from MainWindow)
```
</action>
<verify>
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~LoggingIntegrationTests" 2>&1 | tail -10</automated>
</verify>
<done>LoggingIntegrationTests pass. Log file created in temp directory with expected content. LogPanelSink type check passes.</done>
</task>
</tasks>
<verification>
- `dotnet test --filter "Category=Unit"` and `--filter "Category=Integration"` both pass
- Strings.resx contains all keys listed in the action section
- Strings.fr.resx contains same key set (verified by comparing key counts)
- TranslationSource.Instance is not null
- PropertyChanged fires with `string.Empty` PropertyName on culture change
</verification>
<success_criteria>
Localization system supports runtime culture switching confirmed by tests. All Phase 1 UI strings defined in EN resx. FR resx has same key set (stubbed). Serilog integration test verifies log file creation.
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation/01-05-SUMMARY.md`
</output>