Avalonia本地化的简单实现

所有代码:https://github.com/bodong1987/AvaloniaSamples/tree/main/AvaloniaLocalization

核心其实就两部分,其一是要实现一个简单的数据源,我这里直接采用了比较简单的办法,直接在执行档目录下创建翻译用的json文件,然后文件名就是Culture的名字。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AvaloniaLocalization.Services
{
    /// <summary>
    /// Class CultureInfoData.
    /// </summary>
    public class CultureInfoData
    {
        /// <summary>
        /// The culture information
        /// </summary>
        public readonly CultureInfo CultureInfo;

        /// <summary>
        /// The path
        /// </summary>
        public readonly string Path;

        /// <summary>
        /// Initializes a new instance of the <see cref="CultureInfoData" /> class.
        /// </summary>
        /// <param name="cultureInfo">The culture information.</param>
        /// <param name="path">The path.</param>
        public CultureInfoData(CultureInfo cultureInfo, string path)
        {
            CultureInfo = cultureInfo;
            Path = path;
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
        public override string ToString()
        {
            return CultureInfo.NativeName;
        }
    }

    public interface ILocalizeService : INotifyPropertyChanged
    {
        /// <summary>
        /// Gets the available cultures.
        /// </summary>
        /// <value>The available cultures.</value>
        List<CultureInfoData> AvailableCultures { get; }

        /// <summary>
        /// Gets or sets the selected culture.
        /// </summary>
        /// <value>The selected culture.</value>
        CultureInfoData SelectedCulture { get; set; }

        /// <summary>
        /// Gets the <see cref="System.String"/> with the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>System.String.</returns>
        string this[string key] { get; }

        /// <summary>
        /// Occurs when [on culture changed].
        /// </summary>
        event EventHandler OnCultureChanged;
    }
}

 

using Avalonia.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace AvaloniaLocalization.Services
{
    internal class LocalizeService : ILocalizeService
    {
        #region Properties
        public string CultureName { get; private set; }
        public string Language => CultureName;

        /// <summary>
        /// The local texts
        /// </summary>
        private Dictionary<string, string> LocalTexts = null;
        #endregion

        #region Interfaces
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when [on culture changed].
        /// </summary>
        public event EventHandler OnCultureChanged;

        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region Localization
        public string this[string key]
        {
            get
            {
                if (LocalTexts != null && LocalTexts.TryGetValue(key, out var text))
                {
                    return text;
                }

                return key;
            }
        }

        /// <summary>
        /// Gets the available cultures.
        /// </summary>
        /// <value>The available cultures.</value>
        public List<CultureInfoData> AvailableCultures { get; private set; } = new List<CultureInfoData>();

        CultureInfoData SelectedCultureCore;
        /// <summary>
        /// Gets or sets the selected culture.
        /// </summary>
        /// <value>The selected culture.</value>
        public CultureInfoData SelectedCulture
        {
            get => SelectedCultureCore;
            set
            {
                if(SelectedCultureCore != value)
                {
                    SelectedCultureCore = value;

                    OnSelectCultureChanged();
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LocalizeService"/> class.
        /// </summary>
        public LocalizeService()
        {
            string assemblyPath = Assembly.GetExecutingAssembly().Location;
            string directory = Path.GetDirectoryName(assemblyPath);

            var locDirectory = Path.Combine(directory, "assets/localization");
            CultureInfoData currentInfo = null;

            if (Directory.Exists(locDirectory))
            {
                var files = Directory.GetFiles(locDirectory, "*.json", SearchOption.AllDirectories);

                foreach (var file in files)
                {
                    var name = Path.GetFileNameWithoutExtension(file);

                    var cultureInfo = CultureInfo.GetCultureInfo(name);

                    if (cultureInfo != null)
                    {
                        var info = new CultureInfoData(cultureInfo, file);

                        if (cultureInfo.Name == CultureInfo.CurrentCulture.Name)
                        {
                            currentInfo = info;
                        }

                        AvailableCultures.Add(info);
                    }
                }
            }

            if(currentInfo != null)
            {
                SelectedCulture = currentInfo;
            }
        }

        private void OnSelectCultureChanged()
        {
            if (SelectedCulture == null)
            {
                LocalTexts = null;
                PostReload();
                return;
            }

            try
            {
                LoadAllSources(SelectedCulture.Path);

                PostReload();
            }
            catch (Exception ee)
            {
                Debug.WriteLine(string.Format("Failed load localization file :{0}\n{1}\n{2}", SelectedCulture.Path, ee.Message, ee.StackTrace));
            }
        }

        private void LoadAllSources(string appLanguageFile)
        {
            LocalTexts = new Dictionary<string, string>();

            List<string> pathes = new List<string>()
                {
                    appLanguageFile
                };

            // add additional path here if you need
            foreach (var plugin in new string[] {})
            {
                var p = Path.Combine(plugin, "assets/localization", Path.GetFileName(appLanguageFile));

                if (File.Exists(p))
                {
                    pathes.Add(p);
                }
            }

            foreach (var path in pathes)
            {
                using (FileStream fs = File.OpenRead(path))
                {
                    using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))
                    {
                        var tempDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(sr.ReadToEnd());

                        if (tempDict != null)
                        {
                            foreach (var pair in tempDict)
                            {
                                if (!LocalTexts.ContainsKey(pair.Key))
                                {
                                    LocalTexts.Add(pair.Key, pair.Value);
                                }
                            }
                        }
                    }
                }
            }
        }

        private void PostReload()
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item"));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
            OnCultureChanged?.Invoke(this, EventArgs.Empty);
        }
        #endregion
    }
}

这些代码就提供了数据源的能力。然后就需要实现一个MarkupExtension,这样就可以直接在xaml中直接使用本地化了:

using Avalonia.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AvaloniaLocalization.Services
{
    /// <summary>
    /// Class LocalizeExtension.
    /// Implements the <see cref="MarkupExtension" />
    /// </summary>
    /// <seealso cref="MarkupExtension" />
    public class LocalizeExtension : MarkupExtension
    {
        /// <summary>
        /// Gets or sets the key.
        /// </summary>
        /// <value>The key.</value>
        public string Key { get; set; }
        /// <summary>
        /// Gets or sets the context.
        /// </summary>
        /// <value>The context.</value>
        public string Context { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="LocalizeExtension"/> class.
        /// </summary>
        /// <param name="key">The key.</param>
        public LocalizeExtension(string key)
        {
            Key = key;
        }

        /// <summary>
        /// Provides the value.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        /// <returns>System.Object.</returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var keyToUse = Key;

            if (!string.IsNullOrWhiteSpace(Context))
            {
                keyToUse = $"{Context}/{Key}";
            }

            var binding = new ReflectionBindingExtension($"[{keyToUse}]")
            {
                Mode = BindingMode.OneWay,
                Source = LocalizationProvider.Service,
            };

            return binding.ProvideValue(serviceProvider);
        }
    }

}
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaLocalization.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ls="clr-namespace:AvaloniaLocalization.Services"        
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaLocalization.Views.MainWindow"
        Icon="/Assets/avalonia-logo.ico"
        Title="AvaloniaLocalization">

    <Design.DataContext>
        <!-- This only sets the DataContext for the previewer in an IDE,
             to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
        <vm:MainWindowViewModel/>
    </Design.DataContext>

    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
        <ComboBox MinWidth="150" 
                  Items="{Binding AvailableCultures}"
                  SelectedItem="{Binding SelectedCulture}"
                  ></ComboBox>

        <Button Content="{ls:Localize File}"></Button>
        <Button Content="{ls:Localize New}"></Button>

        <TextBlock x:Name="textBlock_Code"></TextBlock>
    </StackPanel>

</Window>

      关键代码已经加粗。

    如果是要在代码中直接使用本地化呢?下面就是代码片段:

namespace AvaloniaLocalization.Views
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            textBlock_Code.Text = LocalizationProvider.Service["Test Code"];

            LocalizationProvider.Service.OnCultureChanged += (s, e) =>
            {
                textBlock_Code.Text = LocalizationProvider.Service["Test Code"];
            };
        }
    }
}

 

 

posted @ 2023-04-13 21:36  bodong  阅读(398)  评论(0编辑  收藏  举报