--- 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 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" --- 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 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. @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md @.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 ```csharp public sealed class LanguageChangedMessage : ValueChangedMessage { public LanguageChangedMessage(string cultureCode) : base(cultureCode) { } } ``` Task 1: TranslationSource singleton + EN/FR resx files SharepointToolbox/Localization/TranslationSource.cs, SharepointToolbox/Localization/Strings.resx, SharepointToolbox/Localization/Strings.fr.resx, SharepointToolbox.Tests/Localization/TranslationSourceTests.cs - 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) 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 `` 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. cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~TranslationSourceTests" 2>&1 | tail -10 TranslationSourceTests pass. Missing key returns "[key]". Culture switch fires PropertyChanged with empty property name. Strings.resx contains all required keys. Task 2: Serilog integration tests and App.xaml.cs logging wiring verification SharepointToolbox.Tests/Integration/LoggingIntegrationTests.cs 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) ``` cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~LoggingIntegrationTests" 2>&1 | tail -10 LoggingIntegrationTests pass. Log file created in temp directory with expected content. LogPanelSink type check passes. - `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 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. After completion, create `.planning/phases/01-foundation/01-05-SUMMARY.md`