Build / Desktop, server and tools (push) Successful in 33s
The language list comes from CLDR (official in a UN member state, plus the six UN languages), with autonyms and text direction supplied for the 21 that ICU does not know. Strings move from resx to one JSON file per language, looked up by code, so a language unknown to ICU or MSBuild still loads; tools/strings.cs checks keys and placeholders in CI. Arabic, Hindi and Chinese drafts prove right-to-left and system-font fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
80 lines
2.6 KiB
C#
80 lines
2.6 KiB
C#
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Text.Json;
|
|
|
|
using Avalonia.Data;
|
|
using Avalonia.Markup.Xaml;
|
|
using Avalonia.Markup.Xaml.MarkupExtensions;
|
|
|
|
namespace MeWeb.App.Services;
|
|
|
|
public sealed class Localizer : INotifyPropertyChanged
|
|
{
|
|
readonly Dictionary<string, string> english = Load(Languages.Fallback.Code);
|
|
Dictionary<string, string> current;
|
|
|
|
public Localizer() => current = english;
|
|
|
|
public static Localizer Instance { get; } = new();
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
public Language Language { get; private set; } = Languages.Fallback;
|
|
|
|
public CultureInfo Culture { get; private set; } = CultureInfo.GetCultureInfo(Languages.Fallback.Code);
|
|
|
|
public bool IsRightToLeft => Language.Rtl;
|
|
|
|
public string this[string key] => current.GetValueOrDefault(key) ?? english.GetValueOrDefault(key) ?? key;
|
|
|
|
public string Format(string key, params object[] args) => string.Format(Culture, this[key], args);
|
|
|
|
public void SetLanguage(Language language)
|
|
{
|
|
Language = language ?? Languages.Fallback;
|
|
current = Language.Code == Languages.Fallback.Code ? english : Load(Language.Code);
|
|
Culture = CultureFor(Language.Code);
|
|
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = Culture;
|
|
CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = Culture;
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
|
|
}
|
|
|
|
public static bool Has(string code) => typeof(Localizer).Assembly.GetManifestResourceInfo(ResourceName(code)) is not null;
|
|
|
|
static string ResourceName(string code) => $"strings.{code}.json";
|
|
|
|
static Dictionary<string, string> Load(string code)
|
|
{
|
|
using var stream = typeof(Localizer).Assembly.GetManifestResourceStream(ResourceName(code));
|
|
if (stream is null) return [];
|
|
return JsonSerializer.Deserialize(stream, AppJsonContext.Default.DictionaryStringString) ?? [];
|
|
}
|
|
|
|
static CultureInfo CultureFor(string code)
|
|
{
|
|
try
|
|
{
|
|
return CultureInfo.GetCultureInfo(code);
|
|
}
|
|
catch (CultureNotFoundException)
|
|
{
|
|
return CultureInfo.InvariantCulture;
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class TrExtension : MarkupExtension
|
|
{
|
|
public TrExtension()
|
|
{
|
|
}
|
|
|
|
public TrExtension(string key) => Key = key;
|
|
|
|
public string Key { get; set; }
|
|
|
|
public override object ProvideValue(IServiceProvider serviceProvider) =>
|
|
new ReflectionBindingExtension($"[{Key}]") { Mode = BindingMode.OneWay, Source = Localizer.Instance }.ProvideValue(serviceProvider);
|
|
}
|