!794 feat(#I29N8T): TableColumn support localization

* docs: BindItem 支持多语言
* docs: 增加 BindItem 多语言示例
* feat: 设计 ColumnNameAttribute 标签用于支持列头多语言
This commit is contained in:
Argo 2020-12-22 15:38:42 +08:00
parent 5141d5144a
commit d26df34bda
7 changed files with 140 additions and 15 deletions

View File

@ -29,5 +29,13 @@
"IntroText1": "For displaying anchor hyperlinks on page and jumping between them.",
"IntroText2": "Click <code>Anchor</code> item try it",
"ContentText1": "<p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p><p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p><p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p><p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p><p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p>"
},
"BootstrapBlazor.Shared.Pages.BindItem": {
"Name": "Name",
"DateTime": "DateTime",
"Address": "Address",
"Count": "Count",
"Complete": "Complete",
"Education": "Education"
}
}

View File

@ -29,5 +29,13 @@
"IntroText1": "需要展现当前页面上可供跳转的锚点链接,以及快速在锚点之间跳转",
"IntroText2": "点击下面 <code>Anchor</code> 项目,页面滚动到相对应的章节",
"ContentText1": "<p>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。</p><p>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</p><p>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</p><p>简化流程:设计简洁直观的操作流程;</p><p>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</p><p>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</p>"
},
"BootstrapBlazor.Shared.Pages.BindItem": {
"Name": "姓名",
"DateTime": "日期",
"Address": "地址",
"Count": "数量",
"Complete": "是/否",
"Education": "学历"
}
}

View File

@ -601,6 +601,8 @@ namespace BootstrapBlazor.Shared.Pages
[Table("Test")]
public class BindItem
{
// 列头信息支持 ColumnName Display DisplayName 三种标签
/// <summary>
///
/// </summary>
@ -613,22 +615,22 @@ namespace BootstrapBlazor.Shared.Pages
/// <summary>
///
/// </summary>
[Display(Name = "姓名")]
[Required(ErrorMessage = "{0}不能为空")]
[AutoGenerateColumn(Order = 10)]
[ColumnName(Name = "姓名", ResourceName = "Name", ResourceType = typeof(BindItem))]
public string? Name { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = "日期")]
[AutoGenerateColumn(Order = 1, FormatString = "yyyy-MM-dd", Width = 180)]
[ColumnName(Name = "日期", ResourceName = "DateTime", ResourceType = typeof(BindItem))]
public DateTime? DateTime { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = "地址")]
[ColumnName(Name = "地址", ResourceName = "Address", ResourceType = typeof(BindItem))]
[Required(ErrorMessage = "{0}不能为空")]
[AutoGenerateColumn(Order = 20)]
public string? Address { get; set; }
@ -636,14 +638,14 @@ namespace BootstrapBlazor.Shared.Pages
/// <summary>
///
/// </summary>
[Display(Name = "数量")]
[ColumnName(Name = "数量", ResourceName = "Count", ResourceType = typeof(BindItem))]
[AutoGenerateColumn(Order = 40)]
public int Count { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = "是/否")]
[ColumnName(Name = "是/否", ResourceName = "Complete", ResourceType = typeof(BindItem))]
[AutoGenerateColumn(Order = 50)]
public bool Complete { get; set; }
@ -651,7 +653,7 @@ namespace BootstrapBlazor.Shared.Pages
///
/// </summary>
[Required(ErrorMessage = "请选择{0}")]
[Display(Name = "学历")]
[ColumnName(Name = "学历", ResourceName = "Education", ResourceType = typeof(BindItem))]
[AutoGenerateColumn(Order = 60)]
//[EnumConverter(typeof(EnumEducation))]
public EnumEducation? Education { get; set; }

View File

@ -0,0 +1,57 @@
// **********************************
// 框架名称BootstrapBlazor
// 框架作者Argo Zhang
// 开源地址:
// Gitee : https://gitee.com/LongbowEnterprise/BootstrapBlazor
// GitHub: https://github.com/ArgoZhang/BootstrapBlazor
// 开源协议Apache-2.0 (https://gitee.com/LongbowEnterprise/BootstrapBlazor/blob/dev/LICENSE)
// **********************************
using BootstrapBlazor.Localization.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
namespace BootstrapBlazor.Components
{
/// <summary>
/// ColumnName 属性标签用于标示列头显示信息支持多语言
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ColumnNameAttribute : Attribute
{
/// <summary>
/// 获得/设置 列名称
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 获得/设置 资源文件键值名称 为空时使用当前属性名
/// </summary>
public string? ResourceName { get; set; }
/// <summary>
/// 获得/设置 资源文件所在程序集 为空时扫描当前程序集
/// </summary>
public Type? ResourceType { get; set; }
/// <summary>
/// 获得显示名称方法
/// </summary>
/// <returns></returns>
public string? GetName()
{
var ret = Name;
if (ResourceType != null && !string.IsNullOrEmpty(ResourceName))
{
var options = ServiceProviderHelper.ServiceProvider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
var loggerFactory = ServiceProviderHelper.ServiceProvider.GetRequiredService<ILoggerFactory>();
var factory = new JsonStringLocalizerFactory(options, loggerFactory);
var localizer = factory.Create(ResourceType);
ret = localizer[ResourceName];
}
return ret;
}
}
}

View File

@ -51,6 +51,8 @@ namespace Microsoft.Extensions.DependencyInjection
}
});
ServiceProviderHelper.RegisterService(services);
return services;
}
}

View File

@ -12,6 +12,8 @@ using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
namespace Microsoft.AspNetCore.Components.Forms
@ -21,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.Forms
/// </summary>
public static class FieldIdentifierExtensions
{
private static ConcurrentDictionary<(Type ModelType, string FieldName), string> DisplayNameCache { get; } = new ConcurrentDictionary<(Type, string), string>();
private static ConcurrentDictionary<(string CultureInfoName, Type ModelType, string FieldName), string> DisplayNameCache { get; } = new ConcurrentDictionary<(string, Type, string), string>();
private static ConcurrentDictionary<(Type ModelType, string FieldName), string> PlaceHolderCache { get; } = new ConcurrentDictionary<(Type, string), string>();
private static ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> PropertyInfoCache { get; } = new ConcurrentDictionary<(Type, string), PropertyInfo>();
@ -49,19 +51,27 @@ namespace Microsoft.AspNetCore.Components.Forms
/// <returns></returns>
public static string GetDisplayName(this Type modelType, string fieldName)
{
var cacheKey = (Type: modelType, FieldName: fieldName);
var cacheKey = (CultureInfoName: CultureInfo.CurrentUICulture.Name, Type: modelType, FieldName: fieldName);
if (!DisplayNameCache.TryGetValue(cacheKey, out var dn))
{
if (TryGetValidatableProperty(cacheKey.Type, cacheKey.FieldName, out var propertyInfo))
{
var displayNameAttribute = propertyInfo!.GetCustomAttribute<DisplayAttribute>();
if (displayNameAttribute != null)
var colNameAttribute = propertyInfo.GetCustomAttribute<ColumnNameAttribute>();
if (colNameAttribute != null)
{
dn = displayNameAttribute.Name;
dn = colNameAttribute.GetName();
}
else
if (string.IsNullOrEmpty(dn))
{
var displayAttribute = propertyInfo!.GetCustomAttribute<DisplayNameAttribute>();
var displayNameAttribute = propertyInfo.GetCustomAttribute<DisplayAttribute>();
if (displayNameAttribute != null)
{
dn = displayNameAttribute.Name;
}
}
if (string.IsNullOrEmpty(dn))
{
var displayAttribute = propertyInfo.GetCustomAttribute<DisplayNameAttribute>();
if (displayAttribute != null)
{
dn = displayAttribute.DisplayName;
@ -106,7 +116,7 @@ namespace Microsoft.AspNetCore.Components.Forms
{
if (TryGetValidatableProperty(cacheKey.Type, cacheKey.FieldName, out var propertyInfo))
{
var placeHolderAttribute = propertyInfo!.GetCustomAttribute<PlaceHolderAttribute>();
var placeHolderAttribute = propertyInfo.GetCustomAttribute<PlaceHolderAttribute>();
if (placeHolderAttribute != null)
{
dn = placeHolderAttribute.Text;
@ -121,7 +131,7 @@ namespace Microsoft.AspNetCore.Components.Forms
return dn;
}
private static bool TryGetValidatableProperty(Type modelType, string fieldName, out PropertyInfo? propertyInfo)
private static bool TryGetValidatableProperty(Type modelType, string fieldName, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
{
var cacheKey = (ModelType: modelType, FieldName: fieldName);
if (!PropertyInfoCache.TryGetValue(cacheKey, out propertyInfo))

View File

@ -0,0 +1,38 @@
// **********************************
// 框架名称BootstrapBlazor
// 框架作者Argo Zhang
// 开源地址:
// Gitee : https://gitee.com/LongbowEnterprise/BootstrapBlazor
// GitHub: https://github.com/ArgoZhang/BootstrapBlazor
// 开源协议Apache-2.0 (https://gitee.com/LongbowEnterprise/BootstrapBlazor/blob/dev/LICENSE)
// **********************************
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Diagnostics.CodeAnalysis;
namespace BootstrapBlazor.Components
{
/// <summary>
///
/// </summary>
internal static class ServiceProviderHelper
{
/// <summary>
///
/// </summary>
public static ServiceProvider ServiceProvider { get { return _lazy.Value; } }
[NotNull]
private static Lazy<ServiceProvider>? _lazy;
/// <summary>
///
/// </summary>
/// <param name="services"></param>
public static void RegisterService(IServiceCollection services)
{
_lazy = new Lazy<ServiceProvider>(() => services.BuildServiceProvider());
}
}
}