---
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>
