diff --git a/JackCraft.I18N/Converters/I18NConverter.cs b/JackCraft.I18N/Converters/I18NConverter.cs new file mode 100644 index 0000000..3f29d8b --- /dev/null +++ b/JackCraft.I18N/Converters/I18NConverter.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace JackCraft.I18N.Converters; + +internal class I18NConverter(I18NBinding binding, object[] bindingValues) : IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values.Count < 2 || values[0] is not CultureInfo) + return null; + if (binding.KeyConverter.Convert(values[1], null!, null, culture) is not string key) + return values[1]; + var value = I18NConfig.Manager.Get(key, values[0] as CultureInfo); + if (value is string str && bindingValues.Length > 0) + value = string.Format(str, bindingValues); + return binding.ValueConverter.Convert(value, null!, null, culture); + } +} \ No newline at end of file diff --git a/JackCraft.I18N/Converters/I18NKeyConverter.cs b/JackCraft.I18N/Converters/I18NKeyConverter.cs new file mode 100644 index 0000000..61fb947 --- /dev/null +++ b/JackCraft.I18N/Converters/I18NKeyConverter.cs @@ -0,0 +1,22 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace JackCraft.I18N.Converters; + +internal class I18NKeyConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value switch + { + Enum e => value.GetType().Name + "." + e, + string str => str, + _ => null + }; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return null; + } +} \ No newline at end of file diff --git a/JackCraft.I18N/Converters/I18NValueConverter.cs b/JackCraft.I18N/Converters/I18NValueConverter.cs new file mode 100644 index 0000000..64786b8 --- /dev/null +++ b/JackCraft.I18N/Converters/I18NValueConverter.cs @@ -0,0 +1,17 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace JackCraft.I18N.Converters; + +internal class I18NValueConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return null; + } +} \ No newline at end of file diff --git a/JackCraft.I18N/I18NBinding.cs b/JackCraft.I18N/I18NBinding.cs new file mode 100644 index 0000000..1e91511 --- /dev/null +++ b/JackCraft.I18N/I18NBinding.cs @@ -0,0 +1,23 @@ +using Avalonia.Data; +using Avalonia.Data.Converters; +using JackCraft.I18N.Converters; + +namespace JackCraft.I18N; + +internal class I18NBinding : MultiBinding +{ + public readonly IValueConverter KeyConverter; + public readonly IValueConverter ValueConverter; + + public I18NBinding(string key, params dynamic[] values) + { + Mode = BindingMode.OneWay; + TargetNullValue = key; + FallbackValue = key; + Converter = new I18NConverter(this, values); + KeyConverter = new I18NKeyConverter(); + ValueConverter = new I18NValueConverter(); + Bindings.Add(new Binding { Source = I18NConfig.Manager, Path = nameof(I18NConfig.Manager.Culture) }); + Bindings.Add(new Binding { Source = key }); + } +} \ No newline at end of file diff --git a/JackCraft.I18N/I18NConfig.cs b/JackCraft.I18N/I18NConfig.cs new file mode 100644 index 0000000..4265a8c --- /dev/null +++ b/JackCraft.I18N/I18NConfig.cs @@ -0,0 +1,12 @@ +using System.Globalization; +using System.Reflection; + +namespace JackCraft.I18N; + +internal static class I18NConfig +{ + public static I18NManager Manager { get; set; } = null!; + public static string BaseName { get; set; } = string.Empty; + public static Assembly Assembly { get; set; } = typeof(I18NConfig).Assembly; + public static CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; +} \ No newline at end of file diff --git a/JackCraft.I18N/I18NExtension.cs b/JackCraft.I18N/I18NExtension.cs new file mode 100644 index 0000000..67fe230 --- /dev/null +++ b/JackCraft.I18N/I18NExtension.cs @@ -0,0 +1,15 @@ +using Avalonia.Markup.Xaml; + +namespace JackCraft.I18N; + +public class I18NExtension(string key, params object[] values) : MarkupExtension +{ + public I18NExtension(string key) : this(key, []) + { + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return new I18NBinding(key, values); + } +} \ No newline at end of file diff --git a/JackCraft.I18N/I18NManager.cs b/JackCraft.I18N/I18NManager.cs new file mode 100644 index 0000000..25a74bb --- /dev/null +++ b/JackCraft.I18N/I18NManager.cs @@ -0,0 +1,48 @@ +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; + +namespace JackCraft.I18N; + +public sealed class I18NManager : INotifyPropertyChanged +{ + public I18NManager(string baseName, Assembly assembly) + { + I18NConfig.BaseName = baseName; + I18NConfig.Assembly = assembly; + I18NConfig.Manager = this; + } + + public CultureInfo Culture + { + get => I18NConfig.Culture; + set + { + if (Equals(value, I18NConfig.Culture)) return; + I18NConfig.Culture = value; + OnPropertyChanged(); + } + } + + public static string BaseName => I18NConfig.BaseName; + public static Assembly Assembly => I18NConfig.Assembly; + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public T? Get(string key, CultureInfo? culture = null, params object?[] values) + { + culture ??= Culture; + var resourceManager = new ResourceManager(BaseName, Assembly); + var resourceSet = resourceManager.GetResourceSet(culture, true, true); + var obj = resourceSet?.GetObject(key); + if (obj is string str && values.Length > 0) return (dynamic)string.Format(str, values); + if (obj != null) return (dynamic)obj; + return default; + } +} \ No newline at end of file diff --git a/JackCraft.I18N/JackCraft.I18N.csproj b/JackCraft.I18N/JackCraft.I18N.csproj index 00e99c8..40898b0 100644 --- a/JackCraft.I18N/JackCraft.I18N.csproj +++ b/JackCraft.I18N/JackCraft.I18N.csproj @@ -7,4 +7,8 @@ enable + + + +