feat(01-05): implement TranslationSource singleton + EN/FR resx files

- TranslationSource singleton with INotifyPropertyChanged indexer binding
- PropertyChanged fires with string.Empty on culture switch (signals all bindings refresh)
- Missing key returns [key] placeholder (prevents null in WPF bindings)
- Strings.resx with 27 Phase 1 UI string keys (EN)
- Strings.fr.resx with same 27 keys stubbed with EN text (FR completeness Phase 5)
- Strings.Designer.cs ResourceManager for dotnet build compatibility
- SharepointToolbox.csproj updated with EmbeddedResource metadata
This commit is contained in:
Dev
2026-04-02 12:16:57 +02:00
parent 8a58140f9b
commit a287ed83ab
5 changed files with 434 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SharepointToolbox.Localization {
using System;
using System.Reflection;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// Auto-generated designer file for Strings.resx — do not edit manually.
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SharepointToolbox.Localization.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!-- FR stub — Phase 5 -->
<data name="app.title" xml:space="preserve">
<value>SharePoint Toolbox</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="toolbar.connect" xml:space="preserve">
<value>Connect</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="toolbar.manage" xml:space="preserve">
<value>Manage Profiles...</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="toolbar.clear" xml:space="preserve">
<value>Clear Session</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.permissions" xml:space="preserve">
<value>Permissions</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.storage" xml:space="preserve">
<value>Storage</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.search" xml:space="preserve">
<value>File Search</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.duplicates" xml:space="preserve">
<value>Duplicates</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.templates" xml:space="preserve">
<value>Templates</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.bulk" xml:space="preserve">
<value>Bulk Operations</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.structure" xml:space="preserve">
<value>Folder Structure</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.settings" xml:space="preserve">
<value>Settings</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="tab.comingsoon" xml:space="preserve">
<value>Coming soon</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="btn.cancel" xml:space="preserve">
<value>Cancel</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="settings.language" xml:space="preserve">
<value>Language</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="settings.lang.en" xml:space="preserve">
<value>English</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="settings.lang.fr" xml:space="preserve">
<value>French</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="settings.folder" xml:space="preserve">
<value>Data output folder</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="settings.browse" xml:space="preserve">
<value>Browse...</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="profile.name" xml:space="preserve">
<value>Profile name</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="profile.url" xml:space="preserve">
<value>Tenant URL</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="profile.clientid" xml:space="preserve">
<value>Client ID</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="profile.add" xml:space="preserve">
<value>Add</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="profile.rename" xml:space="preserve">
<value>Rename</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="profile.delete" xml:space="preserve">
<value>Delete</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="status.ready" xml:space="preserve">
<value>Ready</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="status.cancelled" xml:space="preserve">
<value>Operation cancelled</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="err.auth.failed" xml:space="preserve">
<value>Authentication failed. Check tenant URL and Client ID.</value>
<!-- FR stub — Phase 5 -->
</data>
<data name="err.generic" xml:space="preserve">
<value>An error occurred. See log for details.</value>
<!-- FR stub — Phase 5 -->
</data>
</root>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="app.title" xml:space="preserve">
<value>SharePoint Toolbox</value>
</data>
<data name="toolbar.connect" xml:space="preserve">
<value>Connect</value>
</data>
<data name="toolbar.manage" xml:space="preserve">
<value>Manage Profiles...</value>
</data>
<data name="toolbar.clear" xml:space="preserve">
<value>Clear Session</value>
</data>
<data name="tab.permissions" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="tab.storage" xml:space="preserve">
<value>Storage</value>
</data>
<data name="tab.search" xml:space="preserve">
<value>File Search</value>
</data>
<data name="tab.duplicates" xml:space="preserve">
<value>Duplicates</value>
</data>
<data name="tab.templates" xml:space="preserve">
<value>Templates</value>
</data>
<data name="tab.bulk" xml:space="preserve">
<value>Bulk Operations</value>
</data>
<data name="tab.structure" xml:space="preserve">
<value>Folder Structure</value>
</data>
<data name="tab.settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="tab.comingsoon" xml:space="preserve">
<value>Coming soon</value>
</data>
<data name="btn.cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="settings.language" xml:space="preserve">
<value>Language</value>
</data>
<data name="settings.lang.en" xml:space="preserve">
<value>English</value>
</data>
<data name="settings.lang.fr" xml:space="preserve">
<value>French</value>
</data>
<data name="settings.folder" xml:space="preserve">
<value>Data output folder</value>
</data>
<data name="settings.browse" xml:space="preserve">
<value>Browse...</value>
</data>
<data name="profile.name" xml:space="preserve">
<value>Profile name</value>
</data>
<data name="profile.url" xml:space="preserve">
<value>Tenant URL</value>
</data>
<data name="profile.clientid" xml:space="preserve">
<value>Client ID</value>
</data>
<data name="profile.add" xml:space="preserve">
<value>Add</value>
</data>
<data name="profile.rename" xml:space="preserve">
<value>Rename</value>
</data>
<data name="profile.delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="status.ready" xml:space="preserve">
<value>Ready</value>
</data>
<data name="status.cancelled" xml:space="preserve">
<value>Operation cancelled</value>
</data>
<data name="err.auth.failed" xml:space="preserve">
<value>Authentication failed. Check tenant URL and Client ID.</value>
</data>
<data name="err.generic" xml:space="preserve">
<value>An error occurred. See log for details.</value>
</data>
</root>

View File

@@ -0,0 +1,38 @@
using System.ComponentModel;
using System.Globalization;
using System.Resources;
namespace SharepointToolbox.Localization;
/// <summary>
/// Singleton INotifyPropertyChanged string lookup enabling runtime culture switching without restart.
/// WPF bindings use: Source={x:Static loc:TranslationSource.Instance}, Path=[key]
/// On CurrentCulture change, fires PropertyChanged with empty string to signal all properties changed,
/// which causes WPF to re-evaluate all bindings to this source.
/// </summary>
public class TranslationSource : INotifyPropertyChanged
{
public static readonly TranslationSource Instance = new();
private ResourceManager _resourceManager = Strings.ResourceManager;
private CultureInfo _currentCulture = CultureInfo.CurrentUICulture;
private TranslationSource() { }
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;
}

View File

@@ -15,6 +15,16 @@
<Page Include="App.xaml" /> <Page Include="App.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localization\Strings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Localization\Strings.fr.resx">
<DependentUpon>Strings.resx</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />