Files
MeWeb/src/MeWeb.App/Services/Localizer.cs
T
thepraandClaude Opus 5.5 8eaf9f36fc
Build / Desktop, server and tools (push) Successful in 33s
Every official language, in JSON: 113 listed, five shipped so far
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
2026-10-15 01:18:13 +02:00

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);
}