一个名为XL_Language C# WPF类库,实现多语言功能,满足您在MVVM架构中使用的需求。提供简洁的API供调用。
使用说明
-
项目引用:
将此XL_Language类库添加到您的WPF项目引用中。 -
语言文件结构:
- 在应用程序根目录创建"Language"文件夹
- 在此文件夹中创建语言文件,如:
- Chinese.json(中文)
- English.json(英文)
-
语言文件格式(JSON):
{ "App": { "Name": "我的应用程序", "Title": "主窗口标题" }, "Button": { "OK": "确定", "Cancel": "取消" }, "Message": { "Welcome": "欢迎使用本软件" } }
-
在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>
-
在代码中使用:
// 设置语言 Lang.Set("Chinese"); Lang.Set("English"); // 获取文本 string appName = Lang.Get("App.Name"); // 或使用简化方式 string okButtonText = "Button.OK".L();
-
在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);
}
}
李枭龙8 个月前
AI生成文章:请以上所有知识进行深入分析,确定主要知识点,为每个知识点撰写详细说明并附上具有代表性且带有清晰注释的代码示例,接着根据内容拟定一个准确反映文档核心的标题,最后严格按照 Markdown 格式进行排版,确保文档规范美观,以满足初学者学习使用的需求。
李枭龙1 年前
X Lucas