You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

四、核心优势说明

  1. 严格遵循Blazor绑定范式:使用内置InputBase组件的Value/ValueChanged/ValueExpression三元组,确保数据同步可靠。
  2. 自动维护字典状态:开关状态通过属性的get/set自动处理字典的增删,无需在UI层写复杂逻辑。
  3. 双重编辑器支持:同时提供内置类型匹配编辑器和自定义模板,兼顾易用性与扩展性。
  4. 清晰的UI结构:完全符合需求中的布局,默认编辑器始终显示,开关控制本地化编辑器可见性。

内容的提问来源于stack exchange,提问作者John Askew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 18:53:13