Blazor中通用本地化编辑器高阶组件实现咨询
符合Blazor范式的通用本地化编辑器实现方案
一、先优化基础模型Localization<T>
首先完善基础类,确保字典默认初始化,避免空引用问题:
public class Localization<T> { public T Default { get; set; } public Dictionary<string, T> Localizations { get; set; } = new(); public Localization(T @default, Dictionary<string, T>? localizations = null) { Default = @default; if (localizations != null) { Localizations = localizations; } } }
二、核心泛型组件LocalizationEditor.razor
实现满足需求的UI结构,同时处理双向绑定、开关逻辑和字典维护:
@typeparam T @inject IStringLocalizer<LocalizationEditor> Loc <div class="localization-editor"> <!-- 默认值编辑行 --> <div class="row align-items-center"> <label class="col-sm-3 col-form-label">@LabelText</label> <div class="col-sm-9"> @RenderDefaultEditor() </div> </div> <!-- 本地化开关行 --> <div class="row align-items-center mt-2"> <div class="col-sm-12"> <div class="form-check"> <input type="checkbox" class="form-check-input" @bind="IsLocalizationEnabled" /> <label class="form-check-label">@Loc["Enable localization for {0}", CurrentCulture]</label> </div> </div> </div> <!-- 本地化值编辑行(开关开启时显示) --> @if (IsLocalizationEnabled) { <div class="row align-items-center mt-2"> <label class="col-sm-3 col-form-label">@Loc["Localized value ({0})", CurrentCulture]</label> <div class="col-sm-9"> @RenderLocalizedEditor() </div> </div> } </div> @code { [Parameter, EditorRequired] public Localization<T> Localization { get; set; } = default!; [Parameter] public string LabelText { get; set; } = "Default Value"; [Parameter, EditorRequired] public string CurrentCulture { get; set; } = default!; // 开关状态与字典自动联动 private bool IsLocalizationEnabled { get => Localization.Localizations.ContainsKey(CurrentCulture); set { if (value) { // 开启时自动添加默认值副本 if (!Localization.Localizations.ContainsKey(CurrentCulture)) { Localization.Localizations[CurrentCulture] = Localization.Default; } } else { // 关闭时自动移除对应文化项 Localization.Localizations.Remove(CurrentCulture); } } } private RenderFragment RenderDefaultEditor() => builder => { BuildEditor(builder, ref Localization.Default); }; private RenderFragment RenderLocalizedEditor() => builder => { var localizedValue = Localization.Localizations[CurrentCulture]; BuildEditor(builder, ref localizedValue); }; // 类型驱动的编辑器渲染逻辑 private void BuildEditor(RenderTreeBuilder builder, ref T value) { // 优先使用父组件传入的自定义模板 if (EditorTemplate != null) { builder.OpenComponent<EditorTemplateWrapper<T>>(0); builder.AddAttribute(1, nameof(EditorTemplateWrapper<T>.Value), value); builder.AddAttribute(2, nameof(EditorTemplateWrapper<T>.ValueChanged), EventCallback.Factory.Create<T>(this, val => { value = val; if (IsLocalizationEnabled && ReferenceEquals(value, Localization.Localizations[CurrentCulture])) { Localization.Localizations[CurrentCulture] = value; } })); builder.AddAttribute(3, nameof(EditorTemplateWrapper<T>.ChildContent), EditorTemplate); builder.CloseComponent(); return; } // 默认使用Blazor内置输入组件 switch (typeof(T)) { case Type t when t == typeof(bool): builder.OpenComponent<InputCheckbox>(0); BindInputComponent(builder, ref value); builder.CloseComponent(); break; case Type t when t == typeof(string): builder.OpenComponent<InputText>(0); BindInputComponent(builder, ref value); builder.CloseComponent(); break; case Type t when t == typeof(int): builder.OpenComponent<InputNumber<int>>(0); BindInputComponent(builder, ref value); builder.CloseComponent(); break; // 可扩展支持DateTime、decimal等更多类型 default: throw new NotSupportedException($"未实现类型{typeof(T)}的编辑器"); } } // 通用绑定逻辑复用 private void BindInputComponent<TRender>(RenderTreeBuilder builder, ref T value) { builder.AddAttribute(1, nameof(InputBase<TRender>.Value), (TRender)(object)value); builder.AddAttribute(2, nameof(InputBase<TRender>.ValueChanged), EventCallback.Factory.Create<TRender>(this, val => { value = (T)(object)val; if (IsLocalizationEnabled && ReferenceEquals(value, Localization.Localizations[CurrentCulture])) { Localization.Localizations[CurrentCulture] = value; } })); builder.AddAttribute(3, nameof(InputBase<TRender>.ValueExpression), () => value); } // 支持自定义编辑器模板 [Parameter] public RenderFragment<T>? EditorTemplate { get; set; } // 辅助组件:传递绑定值到自定义模板 public class EditorTemplateWrapper<TValue> : ComponentBase { [Parameter] public TValue Value { get; set; } = default!; [Parameter] public EventCallback<TValue> ValueChanged { get; set; } [Parameter] public RenderFragment<TValue> ChildContent { get; set; } = default!; protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.AddContent(0, ChildContent(Value)); } } }
三、使用示例
1. 内置编辑器用法
@page "/localization-demo" <h3>本地化编辑器演示</h3> <LocalizationEditor T="bool" Localization="BoolLocalization" LabelText="启用功能" CurrentCulture="fr-FR" /> <LocalizationEditor T="string" Localization="StringLocalization" LabelText="欢迎语" CurrentCulture="es-ES" /> <LocalizationEditor T="int" Localization="IntLocalization" LabelText="最大条目数" CurrentCulture="de-DE" /> @code { private Localization<bool> BoolLocalization { get; set; } = new(true); private Localization<string> StringLocalization { get; set; } = new("欢迎!", new Dictionary<string, string> { { "es-ES", "¡Bienvenido!" } }); private Localization<int> IntLocalization { get; set; } = new(100); }
2. 自定义编辑器用法
<LocalizationEditor T="bool" Localization="BoolLocalization" LabelText="启用功能" CurrentCulture="fr-FR"> <EditorTemplate> <ToggleButton Checked="@context" OnCheckedChanged="@(val => context = val)" /> </EditorTemplate> </LocalizationEditor>
四、核心优势说明
- 严格遵循Blazor绑定范式:使用内置
InputBase组件的Value/ValueChanged/ValueExpression三元组,确保数据同步可靠。 - 自动维护字典状态:开关状态通过属性的
get/set自动处理字典的增删,无需在UI层写复杂逻辑。 - 双重编辑器支持:同时提供内置类型匹配编辑器和自定义模板,兼顾易用性与扩展性。
- 清晰的UI结构:完全符合需求中的布局,默认编辑器始终显示,开关控制本地化编辑器可见性。
内容的提问来源于stack exchange,提问作者John Askew
相关产品推荐
相关产品推荐

