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

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
01-foundation 05 execute 3
01-02
SharepointToolbox/Localization/TranslationSource.cs
SharepointToolbox/Localization/Strings.resx
SharepointToolbox/Localization/Strings.fr.resx
SharepointToolbox.Tests/Localization/TranslationSourceTests.cs
SharepointToolbox.Tests/Integration/LoggingIntegrationTests.cs
true
FOUND-08
FOUND-09
truths artifacts key_links
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
path provides contains
SharepointToolbox/Localization/TranslationSource.cs Singleton INotifyPropertyChanged string lookup for runtime culture switching PropertyChangedEventArgs(string.Empty)
path provides
SharepointToolbox/Localization/Strings.resx EN default resource file with all Phase 1 UI strings
path provides
SharepointToolbox/Localization/Strings.fr.resx FR overlay — all keys present, values stubbed with EN text
from to via pattern
SharepointToolbox/Localization/TranslationSource.cs SharepointToolbox/Localization/Strings.resx ResourceManager from Strings class Strings.ResourceManager
from to via pattern
MainWindow.xaml (plan 01-06) SharepointToolbox/Localization/TranslationSource.cs XAML binding: Source={x:Static loc:TranslationSource.Instance}, Path=[key] 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.

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

@.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 `<!-- 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.
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

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

After completion, create `.planning/phases/01-foundation/01-05-SUMMARY.md`