一个名为XL_Language C# WPF类库,实现多语言功能,满足您在MVVM架构中使用的需求。提供简洁的API供调用。

使用说明

  1. 项目引用
    将此XL_Language类库添加到您的WPF项目引用中。

  2. 语言文件结构

    • 在应用程序根目录创建"Language"文件夹
    • 在此文件夹中创建语言文件,如:
      • Chinese.json(中文)
      • English.json(英文)
  3. 语言文件格式(JSON):

    {
     "App": {
       "Name": "我的应用程序",
       "Title": "主窗口标题"
     },
     "Button": {
       "OK": "确定",
       "Cancel": "取消"
     },
     "Message": {
       "Welcome": "欢迎使用本软件"
     }
    }
  4. 在XAML中使用

    <Window 
       xmlns:lang="clr-namespace:XL_Language;assembly=XL_Language"
       Title="{lang:Lang App.Title}">
    
       <Button Content="{lang:Lang Button.OK}" />
       <TextBlock Text="{lang:Lang Message.Welcome}" />
    </Window>
  5. 在代码中使用

    // 设置语言
    Lang.Set("Chinese");
    Lang.Set("English");
    
    // 获取文本
    string appName = Lang.Get("App.Name");
    // 或使用简化方式
    string okButtonText = "Button.OK".L();
  6. 在MVVM ViewModel中使用
    您可以在ViewModel中直接使用Lang.Get()方法,当语言切换时,通过Lang.LanguageChanged事件通知UI更新:

    public class MainViewModel : INotifyPropertyChanged
    {
       public MainViewModel()
       {
           Lang.LanguageChanged += (s, e) => OnPropertyChanged(null);
       }
    
       public string WelcomeMessage => Lang("Message.Welcome");
    
       // INotifyPropertyChanged实现...
    }

该实现具有以下特点:

  • 语言文件使用JSON格式,易于编辑和维护
  • 提供了简洁的API:Lang.Set()切换语言,Lang.Get()"".L()获取文本
  • 支持XAML中直接绑定多语言文本
  • 自动更新所有已加载的UI元素
  • 找不到对应键时显示带括号的键,便于调试
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;

namespace XL_Language
{
    /// <summary>
    /// 多语言管理静态类,提供语言切换和文本获取功能
    /// </summary>
    public static class Lang
    {
        private static readonly LanguageManager _manager = new();

        /// <summary>
        /// 当前语言
        /// </summary>
        public static string CurrentLanguage => _manager.CurrentLanguage;

        /// <summary>
        /// 语言更改事件
        /// </summary>
        public static event EventHandler? LanguageChanged
        {
            add => _manager.LanguageChanged += value;
            remove => _manager.LanguageChanged -= value;
        }

        /// <summary>
        /// 设置当前语言
        /// </summary>
        /// <param name="language">语言名称(如"Chinese"、"English")</param>
        public static void Set(string language)
        {
            _manager.SetLanguage(language);
        }

        /// <summary>
        /// 获取指定键的文本
        /// </summary>
        /// <param name="key">文本键(如"App.Name")</param>
        /// <returns>对应的文本</returns>
        public static string Get(string key)
        {
            return _manager.GetText(key);
        }

        /// <summary>
        /// 获取指定键的文本,简化调用方式 Lang("App.Name")
        /// </summary>
        /// <param name="key">文本键</param>
        /// <returns>对应的文本</returns>
        public static string Invoke(string key)
        {
            return Get(key);
        }

        /// <summary>
        /// 设置语言文件所在文件夹路径
        /// </summary>
        /// <param name="path">文件夹路径</param>
        public static void SetLanguageFolder(string path)
        {
            _manager.LanguageFolder = path;
        }
    }

    internal class LanguageManager : INotifyPropertyChanged
    {
        private string _currentLanguage = "English";
        private Dictionary<string, object> _languageData = [];
        private string _languageFolder = "Language";

        /// <summary>
        /// 当前语言
        /// </summary>
        public string CurrentLanguage
        {
            get => _currentLanguage;
            private set
            {
                if (_currentLanguage != value)
                {
                    _currentLanguage = value;
                    OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// 语言更改事件
        /// </summary>
        public event EventHandler? LanguageChanged;

        /// <summary>
        /// 语言文件所在文件夹
        /// </summary>
        public string LanguageFolder
        {
            get => _languageFolder;
            set
            {
                if (_languageFolder != value)
                {
                    _languageFolder = value;
                    OnPropertyChanged();
                    // 重新加载当前语言
                    LoadLanguageData(CurrentLanguage);
                }
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        /// <summary>
        /// 设置当前语言
        /// </summary>
        /// <param name="language">语言名称</param>
        public void SetLanguage(string language)
        {
            if (CurrentLanguage == language)
                return;

            if (LoadLanguageData(language))
            {
                CurrentLanguage = language;
                LanguageChanged?.Invoke(this, EventArgs.Empty);
                UpdateAllWindows();
            }
        }

        /// <summary>
        /// 获取指定键的文本,修复了嵌套结构解析问题
        /// </summary>
        /// <param name="key">文本键</param>
        /// <returns>对应的文本</returns>
        public string GetText(string key)
        {
            if (string.IsNullOrEmpty(key))
                return string.Empty;

            var keys = key.Split('.');
            var currentLevel = _languageData;

            for (int i = 0; i < keys.Length; i++)
            {
                var k = keys[i];

                // 如果当前级别不包含指定键,返回带括号的键
                if (!currentLevel.TryGetValue(k, out var value))
                {
                    return $"[{key}]";
                }

                // 如果是最后一个键,返回其值
                if (i == keys.Length - 1)
                {
                    return value?.ToString() ?? $"[{key}]";
                }

                // 如果不是最后一个键,尝试将值转换为嵌套字典
                if (value is Dictionary<string, object> nestedDict)
                {
                    currentLevel = nestedDict;
                }
                else
                {
                    // 如果中间节点不是字典,说明路径无效
                    return $"[{key}]";
                }
            }

            return $"[{key}]";
        }

        /// <summary>
        /// 加载语言数据,改进了JSON解析逻辑
        /// </summary>
        /// <param name="language">语言名称</param>
        /// <returns>是否加载成功</returns>
        private bool LoadLanguageData(string language)
        {
            try
            {
                // 获取语言文件路径
                string assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty;
                string filePath = Path.Combine(assemblyPath, LanguageFolder, $"{language}.json");

                // 检查文件是否存在
                if (!File.Exists(filePath))
                {
                    // 尝试查找应用程序目录
                    string appPath = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]) ?? string.Empty;
                    filePath = Path.Combine(appPath, LanguageFolder, $"{language}.json");

                    if (!File.Exists(filePath))
                    {
                        Console.WriteLine($"Language file not found: {filePath}");
                        return false;
                    }
                }

                // 读取并解析JSON文件,使用更精确的嵌套类型
                string jsonContent = File.ReadAllText(filePath);
                var options = new JsonSerializerOptions
                {
                    AllowTrailingCommas = true,
                    ReadCommentHandling = JsonCommentHandling.Skip,
                    PropertyNameCaseInsensitive = true,
                    // 确保嵌套对象正确解析为字典
                    Converters = { new DictionaryConverter() }
                };

                var newData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonContent, options);
                if (newData != null)
                {
                    _languageData = newData;
                    return true;
                }

                Console.WriteLine("Failed to deserialize language file");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error loading language data: {ex.Message}");
            }

            return false;
        }

        /// <summary>
        /// 更新所有窗口的语言绑定
        /// </summary>
        private void UpdateAllWindows()
        {
            if (Application.Current == null) return;

            foreach (var window in Application.Current.Windows.Cast<Window>().ToList())
            {
                UpdateVisualTree(window);
            }
        }

        /// <summary>
        /// 更新视觉树中的所有绑定
        /// </summary>
        /// <param name="element">要更新的元素</param>
        private void UpdateVisualTree(DependencyObject element)
        {
            // 更新元素的语言绑定
            if (element is FrameworkElement frameworkElement)
            {
                // 触发数据上下文更新
                var dataContext = frameworkElement.DataContext;
                frameworkElement.DataContext = null;
                frameworkElement.DataContext = dataContext;

                // 更新所有绑定表达式
                foreach (var prop in frameworkElement.GetType().GetProperties()
                    .Where(p => typeof(DependencyProperty).IsAssignableFrom(p.PropertyType)))
                {
                    var dp = (DependencyProperty)prop.GetValue(frameworkElement)!;
                    frameworkElement.GetBindingExpression(dp)?.UpdateTarget();
                }
            }

            // 递归更新子元素
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
            {
                var child = VisualTreeHelper.GetChild(element, i);
                UpdateVisualTree(child);
            }
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    /// <summary>
    /// 自定义JSON转换器,确保嵌套对象正确解析为字典
    /// </summary>
    internal class DictionaryConverter : System.Text.Json.Serialization.JsonConverter<object>
    {
        public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.StartObject)
            {
                var dictionary = new Dictionary<string, object>();
                while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
                {
                    if (reader.TokenType != JsonTokenType.PropertyName)
                    {
                        continue;
                    }

                    string key = reader.GetString()!;
                    reader.Read();
                    object? value = Read(ref reader, typeof(object), options);

                    if (value != null)
                    {
                        dictionary[key] = value;
                    }
                }
                return dictionary;
            }
            else if (reader.TokenType == JsonTokenType.StartArray)
            {
                var list = new List<object>();
                while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
                {
                    object? value = Read(ref reader, typeof(object), options);
                    if (value != null)
                    {
                        list.Add(value);
                    }
                }
                return list;
            }
            else if (reader.TokenType == JsonTokenType.String)
            {
                return reader.GetString();
            }
            else if (reader.TokenType == JsonTokenType.Number)
            {
                if (reader.TryGetInt64(out long l))
                    return l;
                return reader.GetDouble();
            }
            else if (reader.TokenType == JsonTokenType.True)
            {
                return true;
            }
            else if (reader.TokenType == JsonTokenType.False)
            {
                return false;
            }
            else if (reader.TokenType == JsonTokenType.Null)
            {
                return null;
            }

            // 如果无法识别类型,返回原始JSON字符串
            return JsonSerializer.Deserialize<string>(ref reader, options);
        }

        public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(writer, value, options);
        }
    }

    public static class LangExtensions
    {
        public static string L(this string key) => Lang.Get(key);
    }
    /// <summary>
    /// XAML绑定扩展,用于在UI中绑定多语言文本
    /// </summary>
    [MarkupExtensionReturnType(typeof(string))]
    public class LangExtension : MarkupExtension
    {
        /// <summary>
        /// 文本键
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="key">文本键</param>
        public LangExtension(string key)
        {
            Key = key;
            Lang.LanguageChanged += OnLanguageChanged;
        }

        private void OnLanguageChanged(object? sender, EventArgs e)
        {
            // 当语言改变时,更新所有使用此扩展的绑定
            Application.Current?.Dispatcher.Invoke(() =>
            {
                foreach (var window in Application.Current.Windows.Cast<Window>().ToList())
                {
                    UpdateVisualTree(window);
                }
            });
        }

        private static void UpdateVisualTree(DependencyObject element)
        {
            if (element is FrameworkElement frameworkElement)
            {
                frameworkElement.InvalidateProperty(FrameworkElement.DataContextProperty);

                // 修复:LocalValueEnumerator 不支持 LINQ,需要手动遍历
                var localValues = frameworkElement.GetLocalValueEnumerator();
                while (localValues.MoveNext())
                {
                    var entry = localValues.Current;
                    if (!entry.Property.ReadOnly)
                    {
                        frameworkElement.GetBindingExpression(entry.Property)?.UpdateTarget();
                    }
                }
            }

            // 递归更新子元素
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
            {
                UpdateVisualTree(VisualTreeHelper.GetChild(element, i));
            }
        }

        public override object ProvideValue(IServiceProvider serviceProvider) => Lang.Get(Key);
    }
}