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>
254 lines
11 KiB
Markdown
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>
|