chore: complete v1.0 milestone
All checks were successful
Release zip package / release (push) Successful in 10s
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>
This commit is contained in:
253
.planning/milestones/v1.0-phases/01-foundation/01-05-PLAN.md
Normal file
253
.planning/milestones/v1.0-phases/01-foundation/01-05-PLAN.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user