- Choosing another language now changes every text on the screen at once. Before, the titles, the question about the test from outside and most labels stayed in the previous language until the app was restarted. - On narrow phones the name at the top shrinks to fit instead of slipping under the info button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
97 lines
2.9 KiB
C#
97 lines
2.9 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
|
|
{
|
|
const string Name = "me-web?";
|
|
|
|
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)
|
|
{
|
|
try
|
|
{
|
|
return string.Format(Culture, this[key], args);
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
return this[key];
|
|
}
|
|
}
|
|
|
|
public void SetLanguage(Language language)
|
|
{
|
|
Language = language ?? Languages.Fallback;
|
|
current = Language.Code == Languages.Fallback.Code ? english : Load(Language.Code);
|
|
if (Language.Rtl)
|
|
{
|
|
var shown = current;
|
|
current = english.Keys.Union(shown.Keys).ToDictionary(k => k, k => (shown.GetValueOrDefault(k) ?? english[k]).Replace(Name, $"\u2066{Name}\u2069", StringComparison.Ordinal));
|
|
}
|
|
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);
|
|
}
|