Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
LocalizeExtension.cs
1using Microsoft.Extensions.Localization;
4using System.ComponentModel;
5using System.Globalization;
6using System.Reflection;
7using System.Runtime.CompilerServices;
8using System.Text;
9using Waher.Events;
10
12{
16 [AcceptEmptyServiceProvider]
17 [ContentProperty(nameof(Path))]
18 public class LocalizeExtension : IMarkupExtension<BindingBase>, INotifyPropertyChanged, IDisposable
19 {
20 private static readonly IMultiValueConverter formatConverter = new FormatStringConverter();
21 private static readonly Dictionary<Type, SortedDictionary<string, bool>> missingStrings = [];
22 private static Timer? timer = null;
23
24 private IStringLocalizer? localizer;
25 private bool isDisposed;
26
27 static LocalizeExtension()
28 {
29 Log.Terminating += Log_Terminating;
30 }
31
32 private static Task Log_Terminating(object? sender, EventArgs e)
33 {
34 timer?.Dispose();
35 timer = null;
36
37 return Task.CompletedTask;
38 }
39
43 public IStringLocalizer Localizer
44 {
45 get
46 {
47 this.localizer ??= LocalizationManager.GetStringLocalizer(this.StringResource);
48 return this.localizer;
49 }
50 }
51
52 public string Path { get; set; } = ".";
53 public BindingMode Mode { get; set; } = BindingMode.OneWay;
54 public IValueConverter? Converter { get; set; } = null;
55 public string? ConverterParameter { get; set; } = null;
56 public string? StringFormat { get; set; } = null;
57 public Type? StringResource { get; set; } = null;
58
62 public BindingBase? Arg { get; set; }
63
64 public object ProvideValue(IServiceProvider ServiceProvider)
65 {
66 return (this as IMarkupExtension<BindingBase>).ProvideValue(ServiceProvider);
67 }
68
69 BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
70 {
71 Type ResourcesType = this.StringResource ?? typeof(AppResources);
72
73 // Validate that the Path actually exists on the .resx-backed class
74 if (ResourcesType
75 .GetRuntimeProperties()
76 .FirstOrDefault(pi => pi.Name == this.Path) is null)
77 {
78 ReportMissingString(this.Path, ResourcesType);
79 return new Binding(
80 "Localizer[STRINGNOTDEFINED]",
81 this.Mode,
82 this.Converter,
83 this.ConverterParameter,
84 this.StringFormat,
85 this
86 );
87 }
88
89 // If the user supplied an Arg binding, build a MultiBinding to format it
90 if (this.Arg is not null)
91 {
92 MultiBinding Multi = new()
93 {
94 Mode = this.Mode,
95 Converter = formatConverter,
96 // No need to set ConverterParameter here—the format string comes from the Localizer
97 };
98
99 // 1️⃣ the format string from your resource
100 Multi.Bindings.Add(new Binding(
101 $"Localizer[{this.Path}]",
102 this.Mode,
103 this.Converter,
104 this.ConverterParameter,
105 this.StringFormat,
106 this
107 ));
108
109 // the actual argument
110 Multi.Bindings.Add(this.Arg);
111
112 return Multi;
113 }
114
115 // Otherwise just a normal single Binding to Localizer[Path]
116 return new Binding(
117 $"Localizer[{this.Path}]",
118 this.Mode,
119 this.Converter,
120 this.ConverterParameter,
121 this.StringFormat,
122 this
123 );
124 }
125
131 public static void ReportMissingString(string StringId, Type Type)
132 {
133 lock (missingStrings)
134 {
135 timer?.Dispose();
136 timer = null;
137
138 if (!missingStrings.TryGetValue(Type, out SortedDictionary<string, bool>? PerId))
139 {
140 PerId = [];
141 missingStrings[Type] = PerId;
142 }
143
144 PerId[StringId] = true;
145
146 timer = new Timer(LogAlert, null, 1000, Timeout.Infinite);
147 }
148 }
149
150 private static void LogAlert(object? _)
151 {
152 StringBuilder sb = new();
153
154 sb.AppendLine("Missing localized strings:");
155 sb.AppendLine();
156
157 lock (missingStrings)
158 {
159 foreach (KeyValuePair<Type, SortedDictionary<string, bool>> P in missingStrings)
160 {
161 sb.AppendLine();
162 sb.AppendLine(P.Key.FullName);
163 sb.AppendLine(new string('-', (P.Key.FullName?.Length ?? 0) + 3));
164 sb.AppendLine();
165
166 foreach (string Key in P.Value.Keys)
167 {
168 sb.Append("* ");
169 sb.AppendLine(Key);
170 }
171 }
172
173 missingStrings.Clear();
174 }
175
176 ServiceRef.LogService.LogAlert(sb.ToString());
177 }
178
179 public LocalizeExtension()
180 {
181 if (!DesignMode.IsDesignModeEnabled)
182 LocalizationManager.CurrentCultureChanged += this.OnCurrentCultureChanged;
183 }
184
185 ~LocalizeExtension()
186 {
187 this.Dispose(false);
188 }
189
190 private void OnCurrentCultureChanged(object? sender, CultureInfo culture)
191 {
192 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Localizer)));
193 }
194
198 public void Dispose()
199 {
200 this.Dispose(true);
201 GC.SuppressFinalize(this);
202 }
203
207 protected virtual void Dispose(bool disposing)
208 {
209 if (this.isDisposed)
210 return;
211
212 LocalizationManager.CurrentCultureChanged -= this.OnCurrentCultureChanged;
213
214 this.isDisposed = true;
215 }
216
217 public event PropertyChangedEventHandler? PropertyChanged;
218 }
219
220
221}
A strongly-typed resource class, for looking up localized strings, etc.
static void ReportMissingString(string StringId, Type Type)
Reports a missing string
virtual void Dispose(bool disposing)
IDisposable.Dispose
IStringLocalizer Localizer
Localizer instance reference.
A converter that formats a string using a format string and arguments.
Definition: ImplTypes.g.cs:58