!619 feat(#I22PTA): wasm 支持本地化功能

* refactor: 更新 StringLocalizer 类
* refactor: 删除 PathHelper 文件
* refactor: 根据新的资源文件方式重写解析程序
* refactor: 更改资源文件路径为 Locales
* doc: 增加资源文件
* refactor: 删除带命名空间的资源文件
* refactor: 更新 Resources 文件夹名称
* release: 发布新版本 3.1.27-beta02
* feat: 增加 ICultureStorage 接口适配 wasm 模式
* refactor: 更改方法名为 GetCultureAsync
* refactor: 精简服务
* refactor: 移动 localStorage 存储文化信息代码
* style: 增加语言选择下拉框宽度
* refactor: 更新配置文件仅需配置文化名称即可
* feat: wasm 模式增加本地化支持
* feat: wasm 模式增加文化持久化
* refactor: 移除 useWebAssemblyDebugging 配置项
* feat: 更新本地化服务设置默认使用本机文化信息
* refactor: 更新资源文件打包方式使用卫星文件
* feat: 重构 JsonLocalization 支持 wasm 模式
* refactor: 依赖组件升级到最新版
This commit is contained in:
Argo
2020-10-27 01:56:00 +08:00
parent 8db0881363
commit f4ffd646a5
80 changed files with 582 additions and 542 deletions

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
///
/// </summary>
internal static class CultureStorageExtensions
{
/// <summary>
/// 添加本地化持久化策略服务
/// </summary>
/// <param name="services"></param>
public static IServiceCollection AddCultureStorage(this IServiceCollection services)
{
services.AddSingleton<ICultureStorage, DefaultCultureStorage>();
return services;
}
internal class DefaultCultureStorage : ICultureStorage
{
public CultureStorageMode Mode { get; set; }
}
}
}

View File

@@ -6,7 +6,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -52,6 +51,7 @@ namespace BootstrapBlazor.Server
services.AddControllers();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddCultureStorage();
services.AddBlazorBackgroundTask();
@@ -62,8 +62,8 @@ namespace BootstrapBlazor.Server
services.AddJsonLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = Configuration.GetSupportCultures().Select(kv => new CultureInfo(kv.Value)).ToList();
options.DefaultRequestCulture = new RequestCulture("zh-CN");
var supportedCultures = Configuration.GetSupportCultures().ToList();
options.DefaultRequestCulture = new RequestCulture(CultureInfo.CurrentCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
@@ -77,6 +77,7 @@ namespace BootstrapBlazor.Server
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 启用本地化
app.UseRequestLocalization(app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>().Value);
app.UseForwardedHeaders(new ForwardedHeadersOptions() { ForwardedHeaders = ForwardedHeaders.All });

View File

@@ -7,8 +7,8 @@
}
},
"AllowedHosts": "*",
"SupportCultures": {
"English": "en-US",
"简体中文": "zh-CN"
}
"SupportCultures": [
"en-US",
"zh-CN"
]
}

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
<PackageReference Include="System.Net.Http.Json" Version="3.2.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,7 +1,9 @@
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Microsoft.Extensions.Configuration
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Culture 扩展操作类
@@ -13,10 +15,48 @@ namespace Microsoft.Extensions.Configuration
/// </summary>
/// <param name="configuration"></param>
/// <returns></returns>
public static IEnumerable<KeyValuePair<string, string>> GetSupportCultures(this IConfiguration configuration) => configuration.GetSection("SupportCultures").GetChildren()
.SelectMany(c => new KeyValuePair<string, string>[]
public static IEnumerable<CultureInfo> GetSupportCultures(this IConfiguration configuration)
{
var ret = configuration.GetSection("SupportCultures").GetChildren()
.Select(c => c.Value);
if (!ret.Any())
{
new KeyValuePair<string, string>(c.Key, c.Value)
});
ret = new List<string>()
{
"en-US",
"zh-CN"
};
}
return ret.Select(c => new CultureInfo(c));
}
}
/// <summary>
/// ICulture
/// </summary>
public interface ICultureStorage
{
/// <summary>
///
/// </summary>
public CultureStorageMode Mode { get; set; }
}
/// <summary>
///
/// </summary>
public enum CultureStorageMode
{
/// <summary>
///
/// </summary>
Webapi,
/// <summary>
///
/// </summary>
LocalStorage
}
}

View File

@@ -1,8 +1,10 @@
@using Microsoft.Extensions.Configuration
@using Microsoft.Extensions.DependencyInjection
@using Microsoft.Extensions.Configuration
@using System.Globalization
@inherits BootstrapComponentBase
@inject IConfiguration Configuration
@inject NavigationManager NavigationManager
@inject ICultureStorage CultureStorage
<div @attributes="@AdditionalAttributes" class="@ClassString">
<label>请选择语言:</label>
@@ -10,7 +12,7 @@
<Options>
@foreach (var kv in Configuration.GetSupportCultures())
{
<SelectOption Text="@kv.Key" Value="@kv.Value" />
<SelectOption Text="@kv.NativeName" Value="@kv.Name" />
}
</Options>
</Select>
@@ -23,17 +25,33 @@
private string SelectedCulture { get; set; } = CultureInfo.CurrentUICulture.Name;
private Task SetCulture(SelectedItem item)
private async Task SetCulture(SelectedItem item)
{
if (SelectedCulture != item.Value)
if (CultureStorage.Mode == CultureStorageMode.Webapi)
{
var culture = item.Value;
var uri = new Uri(NavigationManager.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var query = $"?culture={Uri.EscapeDataString(culture)}&redirectUri={Uri.EscapeDataString(uri)}";
// 使用 api 方式 适用于 Server-Side 模式
if (SelectedCulture != item.Value)
{
var culture = item.Value;
var uri = new Uri(NavigationManager.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var query = $"?culture={Uri.EscapeDataString(culture)}&redirectUri={Uri.EscapeDataString(uri)}";
// use a path that matches your culture redirect controller from the previous steps
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
// use a path that matches your culture redirect controller from the previous steps
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
}
}
else
{
var cultureName = item.Value;
if (cultureName != CultureInfo.CurrentCulture.Name)
{
await JSRuntime.InvokeAsync<string>("$.blazorCulture.set", cultureName);
var culture = new CultureInfo(cultureName);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
}
}
return Task.CompletedTask;
}
}

View File

@@ -1141,5 +1141,5 @@ section {
}
.culture-selector .form-select {
width: 120px;
width: 210px;
}

View File

@@ -7,10 +7,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
<PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.1" PrivateAssets="all" />
<PackageReference Include="System.Net.Http.Json" Version="3.2.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,7 +2,9 @@
using BootstrapBlazor.Shared.Data;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using System;
using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
@@ -37,7 +39,28 @@ namespace BootstrapBlazor.WebAssembly.ClientHost
builder.Services.AddSingleton<WeatherForecastService>();
await builder.Build().RunAsync();
builder.Services.AddSingleton<ICultureStorage, DefaultCultureStorage>();
var host = builder.Build();
await GetCultureAsync(host);
await host.RunAsync();
}
// based on https://github.com/pranavkm/LocSample
private static async Task GetCultureAsync(WebAssemblyHost host)
{
var jsRuntime = host.Services.GetRequiredService<IJSRuntime>();
var cultureName = await jsRuntime.InvokeAsync<string>("$.blazorCulture.get") ?? "zh-CN";
var culture = new CultureInfo(cultureName);
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
}
internal class DefaultCultureStorage : ICultureStorage
{
public CultureStorageMode Mode { get; set; } = CultureStorageMode.LocalStorage;
}
}
}

View File

@@ -1,5 +1,4 @@
{
"useWebAssemblyDebugging": true,
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,

View File

@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -34,7 +34,6 @@ namespace BootstrapBlazor.WebAssembly.ServerHost
public void ConfigureServices(IServiceCollection services)
{
services.AddBootstrapBlazor();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -62,7 +61,6 @@ namespace BootstrapBlazor.WebAssembly.ServerHost
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapFallbackToFile("index.html");
});
}

View File

@@ -6,10 +6,15 @@
<RazorLangVersion>3.0</RazorLangVersion>
<IsPackable>true</IsPackable>
<PackageIcon>logo.png</PackageIcon>
<Version>3.1.27-beta01</Version>
<Version>3.1.27-beta02</Version>
<PackageReleaseNotes>更新日志https://gitee.com/LongbowEnterprise/BootstrapBlazor/wikis</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EPPlus" Version="4.5.3.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.1.9" />

View File

@@ -0,0 +1,192 @@
{
"Components.AutoComplete": {
"NoDataTip": "No Data",
"PlaceHolder": "Please Input"
},
"Components.Captcha": {
"HeaderText": "Captcha",
"BarText": "Slide right to fill puzzle",
"FailedText": "Load failed",
"LoadText": "Loading ...",
"TryText": "Try again"
},
"Components.Console": {
"HeaderText": "Monitor",
"LightTitle": "Light",
"ClearButtonText": "Clear",
"AutoScrollText": "AutoScroll"
},
"Components.DateTimePicker": {
"DatePlaceHolder": "Date",
"TimePlaceHolder": "Time",
"DateTimePlaceHolderText": "Please select ...",
"DatePlaceHolderText": "Please select ...",
"TimeFormat": "hh\\:mm\\:ss",
"DateFormat": "M/d/yyyy",
"DateTimeFormat": "M/d/yyyy hh\\:mm\\:ss",
"AiraPrevYearLabel": "Prev Year",
"AiraNextYearLabel": "Next Year",
"AiraPrevMonthLabel": "Prev Month",
"AiraNextMonthLabel": "Next Month",
"ClearButtonText": "Clear",
"NowButtonText": "Now",
"ConfirmButtonText": "Ok",
"CancelButtonText": "Cancel",
"YearText": "{0}",
"MonthText": "{0}",
"YearPeriodText": "{0} - {1}",
"Months": "January,February,March,April,May,June,July,August,September,October,November,December",
"MonthLists": "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
"WeekLists": "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
"GenericTypeErroMessage": "DateTimePicker only support DateTime or Nullable<DateTime>"
},
"Components.DropdownList": {
"PlaceHolder": "Please select ..."
},
"Components.Editor": {
"PlaceHolder": "Click to edit"
},
"Components.EditorForm": {
"ModelInvalidOperationExceptionMessage": "ValidateForm MODEL not match {0} MODEL",
"PlaceHolderText": "Please input ..."
},
"Components.EqualToValidator": {
"ErrorMessage": "Please enter the same value again"
},
"Components.GoTop": {
"TooltipText": "Go top"
},
"Components.Layout": {
"TooltipText": "Click to Expand/Collapse sidebar"
},
"Components.MultiSelect": {
"PlaceHolder": "Click for select ...",
"SelectAllText": "All",
"ReverseSelectText": "Reverse",
"ClearText": "Clear"
},
"Components.Pagination": {
"AiraPageLabel": "Pagination",
"AiraPrevPageText": "Prev",
"AiraFirstPageText": "First",
"AiraNextPageText": "Next",
"PageInfoText": "{0}-{1}",
"TotalInfoText": ", Total {0}",
"PrePageInfoText": "",
"RowInfoText": "",
"LabelString": "page {0}",
"SelectItemsText": "{0} /page"
},
"Components.PopConfirmButton": {
"CloseButtonText": "Cancel",
"ConfirmButtonText": "Ok",
"Content": "Are you sure DELETE?"
},
"Components.QRCode": {
"PlaceHolder": "Please input ...",
"ClearButtonText": "Clear",
"GenerateButtonText": "Generate"
},
"Components.RequiredValidator": {
"ErrorMessage": "This field is required"
},
"Components.Search": {
"SearchButtonText": "Search"
},
"Components.Select": {
"PlaceHolder": "Click for select ..."
},
"Components.StringLengthValidator": {
"ErrorMessage": "Please enter a value less than or equal to {{0}}"
},
"Components.SweetAlert": {
"CloseButtonText": "Close",
"CancelButtonText": "Cancel",
"ConfirmButtonText": "Confirm"
},
"Components.Switch": {
"OnInnerText": "On",
"OffInnerText": "Off"
},
"Components.Table": {
"AddButtonText": "Add",
"EditButtonText": "Edit",
"DeleteButtonText": "Delete",
"CancelDeleteButtonText": "Cancel",
"ConfirmDeleteButtonText": "Delete",
"ConfirmDeleteContentText": "Are you sure to DELETE all selected rows?",
"RefreshButtonText": "Refresh",
"ColumnButtonTitleText": "Show/Hide Columns",
"ColumnButtonText": "Columns",
"ExportButtonText": "Export",
"SearchPlaceholderText": "Search",
"SearchButtonText": "Search",
"ResetButtonText": "Reset",
"AdvanceButtonText": "Advance Search",
"CheckboxDisplayText": "All",
"EditModalTitle": "Edit Data",
"AddModalTitle": "New Data",
"ColumnButtonTemplateHeaderText": "",
"SearchTooltip": "<div class='search-input-tooltip'>Please input ...</br><kbd>Enter</kbd> Search <kbd>ESC</kbd> Clear</div>",
"SearchModalTitle": "Searching",
"AddButtonToastTitle": "Add Data",
"AddButtonToastContent": "Save data failed. Please provider SAVE method",
"EditButtonToastTitle": "Add Data",
"EditButtonToastNotSelectContent": "Save data failed. Please provider SAVE method",
"EditButtonToastMoreSelectContent": "Only one row can be EDIT",
"EditButtonToastNoSaveMethodContent": "Can not EDIT data. Please provider SAVE method",
"SaveButtonToastTitle": "Save Data",
"SaveButtonToastContent": "Save data failed. Please provider SAVE method",
"SaveButtonToastResultContent": "Save data {0}, auto close after {1}s",
"SuccessText": "Successful",
"FailText": "Failed",
"DeleteButtonToastTitle": "Delete Data",
"DeleteButtonToastContent": "Please select DELETE rows, auto claose after {1}s",
"DeleteButtonToastResultContent": "Delete data {0}, auto claose after {1}s"
},
"Components.TableEditorDialog": {
"CloseButtonText": "Close",
"SaveButtonText": "Save"
},
"Components.TableFilter": {
"Title": "Filter",
"ClearButtonText": "Clear",
"FilterButtonText": "Filter",
"BoolFilter.AllText": "All",
"BoolFilter.TrueText": "True",
"BoolFilter.FalseText": "False",
"GreaterThanOrEqual": "GreaterThanOrEqual",
"LessThanOrEqual": "LessThanOrEqual",
"GreaterThan": "GreaterThan",
"LessThan": "LessThan",
"Equal": "Equal",
"NotEqual": "NotEqual",
"Contains": "Contains",
"NotContains": "NotContains",
"EnumFilter.AllText": "All"
},
"Components.TableSearchDialog": {
"ResetButtonText": "Reset",
"QueryButtonText": "Query"
},
"Components.Timer": {
"PauseText": "Pause",
"ResumeText": "Resume",
"CancelText": "Cancel",
"StarText": "Star"
},
"Components.Toggle": {
"OnText": "Expand",
"OffText": "Collapse"
},
"Components.Transfer": {
"LeftPanelText": "List 1",
"RightPanelText": "List 2"
},
"Components.Upload": {
"Text": "Upload",
"ResetText": "Reset",
"AllowFileTypeErrorMessage": "Only allow Images type file",
"FileTooLargeText": "File to large"
}
}

View File

@@ -0,0 +1,192 @@
{
"Components.AutoComplete": {
"NoDataTip": "无匹配数据",
"PlaceHolder": "请输入"
},
"Components.Captcha": {
"HeaderText": "请完成安全验证",
"BarText": "向右滑动填充拼图",
"FailedText": "加载失败",
"LoadText": "正在加载 ...",
"TryText": "再试一次"
},
"Components.Console": {
"HeaderText": "系统监控",
"LightTitle": "通讯指示灯",
"ClearButtonText": "清屏",
"AutoScrollText": "自动滚屏"
},
"Components.DateTimePicker": {
"DatePlaceHolder": "选择日期",
"TimePlaceHolder": "选择时间",
"DateTimePlaceHolderText": "请选择日期时间",
"DatePlaceHolderText": "请选择日期",
"TimeFormat": "hh\\:mm\\:ss",
"DateFormat": "yyyy-MM-dd",
"DateTimeFormat": "yyyy-MM-dd hh\\:mm\\:ss",
"AiraPrevYearLabel": "前一年",
"AiraNextYearLabel": "后一年",
"AiraPrevMonthLabel": "上一月",
"AiraNextMonthLabel": "下一月",
"ClearButtonText": "清空",
"NowButtonText": "此刻",
"ConfirmButtonText": "确定",
"CancelButtonText": "取消",
"YearText": "{0} 年",
"MonthText": "{0} 月",
"YearPeriodText": "{0} 年 - {1} 年",
"Months": "1,2,3,4,5,6,7,8,9,10,11,12",
"MonthLists": "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月",
"WeekLists": "日,一,二,三,四,五,六",
"GenericTypeErroMessage": "DateTimePicker 组件仅支持绑定泛型为 DateTime 或者 DateTime?"
},
"Components.DropdownList": {
"PlaceHolder": "请选择 ..."
},
"Components.Editor": {
"PlaceHolder": "点击后编辑"
},
"Components.EditorForm": {
"ModelInvalidOperationExceptionMessage": "验证表单与 {0} 绑定模型不一致",
"PlaceHolderText": "请输入 ..."
},
"Components.EqualToValidator": {
"ErrorMessage": "你的输入不相同"
},
"Components.GoTop": {
"TooltipText": "返回顶端"
},
"Components.Layout": {
"TooltipText": "点击展开收缩左侧菜单"
},
"Components.MultiSelect": {
"PlaceHolder": "点击进行多选 ...",
"SelectAllText": "全选",
"ReverseSelectText": "反选",
"ClearText": "清除"
},
"Components.Pagination": {
"AiraPageLabel": "分页组件",
"AiraPrevPageText": "上一页",
"AiraFirstPageText": "第一页",
"AiraNextPageText": "下一页",
"PageInfoText": "显示第 {0} 到第 {1} 条记录",
"TotalInfoText": ",总共 {0} 条记录",
"PrePageInfoText": "每页显示",
"RowInfoText": "条记录",
"LabelString": "第 {0} 页",
"SelectItemsText": "{0} 条/页"
},
"Components.PopConfirmButton": {
"CloseButtonText": "关闭",
"ConfirmButtonText": "确定",
"Content": "确认删除吗?"
},
"Components.QRCode": {
"PlaceHolder": "请输入内容",
"ClearButtonText": "清除",
"GenerateButtonText": "生成"
},
"Components.RequiredValidator": {
"ErrorMessage": "这是必填字段"
},
"Components.Search": {
"SearchButtonText": "搜索"
},
"Components.Select": {
"PlaceHolder": "请选择 ..."
},
"Components.StringLengthValidator": {
"ErrorMessage": "最多可以输入 {{0}} 个字符"
},
"Components.SweetAlert": {
"CloseButtonText": "关闭",
"CancelButtonText": "取消",
"ConfirmButtonText": "确认"
},
"Components.Switch": {
"OnInnerText": "开",
"OffInnerText": "关"
},
"Components.Table": {
"AddButtonText": "新建",
"EditButtonText": "编辑",
"DeleteButtonText": "删除",
"CancelDeleteButtonText": "取消",
"ConfirmDeleteButtonText": "删除",
"ConfirmDeleteContentText": "确认要删除选中的所有行吗?",
"RefreshButtonText": "刷新",
"ColumnButtonTitleText": "列显示隐藏控制",
"ColumnButtonText": "列",
"ExportButtonText": "导出数据",
"SearchPlaceholderText": "搜索",
"SearchButtonText": "搜索",
"ResetButtonText": "清空过滤",
"AdvanceButtonText": "高级搜索",
"CheckboxDisplayText": "选择",
"EditModalTitle": "编辑数据窗口",
"AddModalTitle": "新建数据窗口",
"ColumnButtonTemplateHeaderText": "操作",
"SearchTooltip": "<div class='search-input-tooltip'>输入任意字符串全局搜索</br><kbd>Enter</kbd> 搜索 <kbd>ESC</kbd> 清除搜索</div>",
"SearchModalTitle": "搜索条件",
"AddButtonToastTitle": "新建数据",
"AddButtonToastContent": "未提供保存数据方法,无法新建数据",
"EditButtonToastTitle": "编辑数据",
"EditButtonToastNotSelectContent": "请选择要编辑的数据",
"EditButtonToastMoreSelectContent": "只能选择一项要编辑的数据",
"EditButtonToastNoSaveMethodContent": "未提供保存数据方法,无法编辑数据",
"SaveButtonToastTitle": "保存数据",
"SaveButtonToastContent": "未提供保存数据方法,无法保存数据",
"SaveButtonToastResultContent": "保存数据{0}, {1} 秒后自动关闭",
"SuccessText": "成功",
"FailText": "失败",
"DeleteButtonToastTitle": "删除数据",
"DeleteButtonToastContent": "请选择要删除的数据, {0} 秒后自动关闭",
"DeleteButtonToastResultContent": "删除数据{0}, {1} 秒后自动关闭"
},
"Components.TableEditorDialog": {
"CloseButtonText": "关闭",
"SaveButtonText": "保存"
},
"Components.TableFilter": {
"Title": "过滤",
"ClearButtonText": "重置",
"FilterButtonText": "确认",
"BoolFilter.AllText": "全部",
"BoolFilter.TrueText": "选中",
"BoolFilter.FalseText": "未选中",
"GreaterThanOrEqual": "大于等于",
"LessThanOrEqual": "小于等于",
"GreaterThan": "大于",
"LessThan": "小于",
"Equal": "等于",
"NotEqual": "不等于",
"Contains": "包含",
"NotContains": "不包含",
"EnumFilter.AllText": "全选"
},
"Components.TableSearchDialog": {
"ResetButtonText": "重置",
"QueryButtonText": "查询"
},
"Components.Timer": {
"PauseText": "暂停",
"ResumeText": "继续",
"CancelText": "取消",
"StarText": "开始计时"
},
"Components.Toggle": {
"OnText": "展开",
"OffText": "收缩"
},
"Components.Transfer": {
"LeftPanelText": "列表 1",
"RightPanelText": "列表 2"
},
"Components.Upload": {
"Text": "点击上传",
"ResetText": "重置",
"AllowFileTypeErrorMessage": "只允许选择图片类型文件",
"FileTooLargeText": "文件太大"
}
}

View File

@@ -1,18 +0,0 @@
using System.IO;
using System.Reflection;
namespace BootstrapBlazor.Localization.Json
{
/// <summary>
/// 路径操作类
/// </summary>
internal static class PathHelpers
{
/// <summary>
/// 获取当前应用程序所在路径
/// </summary>
/// <returns></returns>
public static string GetApplicationRoot()
=> Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
}

View File

@@ -14,7 +14,7 @@ namespace BootstrapBlazor.Localization.Json
public StringLocalizer(IStringLocalizerFactory factory)
{
_localizer = factory.Create(string.Empty, PathHelpers.GetApplicationRoot());
_localizer = factory.Create(string.Empty, string.Empty);
}
public LocalizedString this[string name] => _localizer[name];

View File

@@ -12,12 +12,7 @@ namespace BootstrapBlazor.Localization.Json
/// </summary>
public JsonLocalizationOptions()
{
ResourcesPath = "Resources";
ResourcesPath = "Locales";
}
/// <summary>
/// 获得/设置 资源定位方式 默认为 TypeBased 基于类型定位 TypeFullName.CultureName.json 格式
/// </summary>
public ResourcesType ResourcesType { get; set; } = ResourcesType.TypeBased;
}
}

View File

@@ -29,8 +29,8 @@ namespace Microsoft.Extensions.DependencyInjection
{
//services.AddSingleton<IHtmlLocalizerFactory, JsonHtmlLocalizerFactory>();
services.TryAddSingleton<IStringLocalizerFactory, JsonStringLocalizerFactory>();
services.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
services.TryAddTransient(typeof(IStringLocalizer), typeof(StringLocalizer));
services.TryAddScoped(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
services.TryAddScoped(typeof(IStringLocalizer), typeof(StringLocalizer));
if (setupAction != null) services.Configure(setupAction);
}

View File

@@ -5,8 +5,8 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
namespace BootstrapBlazor.Localization.Json
{
@@ -17,8 +17,9 @@ namespace BootstrapBlazor.Localization.Json
{
private readonly ConcurrentDictionary<string, IEnumerable<KeyValuePair<string, string>>> _resourcesCache = new ConcurrentDictionary<string, IEnumerable<KeyValuePair<string, string>>>();
private readonly string _resourcesPath;
private readonly Assembly _assembly;
private readonly string? _resourceName;
private readonly string _typeName;
private readonly ILogger _logger;
private string _searchedLocation = "";
@@ -26,12 +27,14 @@ namespace BootstrapBlazor.Localization.Json
/// <summary>
/// 构造函数
/// </summary>
/// <param name="resourcesPath"></param>
/// <param name="assembly"></param>
/// <param name="resourceName"></param>
/// <param name="typeName"></param>
/// <param name="logger"></param>
public JsonStringLocalizer(string resourcesPath, string? resourceName, ILogger logger)
public JsonStringLocalizer(Assembly assembly, string? resourceName, string typeName, ILogger logger)
{
_resourcesPath = resourcesPath;
_assembly = assembly;
_typeName = typeName;
_resourceName = resourceName;
_logger = logger;
}
@@ -115,7 +118,7 @@ namespace BootstrapBlazor.Localization.Json
while (culture != culture.Parent)
{
BuildResourcesCache(culture.Name);
BuildResourcesCache(culture);
if (_resourcesCache.TryGetValue(culture.Name, out var resources))
{
@@ -158,7 +161,7 @@ namespace BootstrapBlazor.Localization.Json
private IEnumerable<string> GetAllResourceStrings(CultureInfo culture)
{
BuildResourcesCache(culture.Name);
BuildResourcesCache(culture);
var ret = Enumerable.Empty<string>();
if (_resourcesCache.TryGetValue(culture.Name, out var resources))
@@ -168,39 +171,23 @@ namespace BootstrapBlazor.Localization.Json
return ret;
}
private void BuildResourcesCache(string culture)
private void BuildResourcesCache(CultureInfo culture)
{
_resourcesCache.GetOrAdd(culture, _ =>
_resourcesCache.GetOrAdd(culture.Name, key =>
{
var resourceFile = string.IsNullOrEmpty(_resourceName)
? $"{culture}.json"
: $"{_resourceName}.{culture}.json";
_searchedLocation = Path.Combine(_resourcesPath, resourceFile);
if (!File.Exists(_searchedLocation))
{
if (resourceFile.Any(r => r == '.'))
{
var resourceFileWithoutExtension = Path.GetFileNameWithoutExtension(resourceFile);
var resourceFileWithoutCulture = resourceFileWithoutExtension.Substring(0, resourceFileWithoutExtension.LastIndexOf('.'));
resourceFile = $"{resourceFileWithoutCulture.Replace('.', Path.DirectorySeparatorChar)}.{culture}.json";
_searchedLocation = Path.Combine(_resourcesPath, resourceFile);
}
}
var value = Enumerable.Empty<KeyValuePair<string, string>>();
if (File.Exists(_searchedLocation))
_searchedLocation = $"{_resourceName}.{key}.json";
using var res = _assembly.GetManifestResourceStream(_searchedLocation);
if (res != null)
{
var builder = new ConfigurationBuilder()
.SetBasePath(_resourcesPath)
.AddJsonFile(resourceFile, optional: false, reloadOnChange: false);
var config = builder.Build();
value = config.AsEnumerable();
var config = new ConfigurationBuilder()
.AddJsonStream(res)
.Build();
value = config.GetSection(_typeName).GetChildren().SelectMany(c => new KeyValuePair<string, string>[] { new KeyValuePair<string, string>(c.Key, c.Value) });
}
return value;
});
}

View File

@@ -2,7 +2,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.IO;
using System.Reflection;
namespace BootstrapBlazor.Localization.Json
@@ -13,7 +12,6 @@ namespace BootstrapBlazor.Localization.Json
internal class JsonStringLocalizerFactory : IStringLocalizerFactory
{
private readonly string _resourcesRelativePath;
private readonly ResourcesType _resourcesType = ResourcesType.TypeBased;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
@@ -24,7 +22,6 @@ namespace BootstrapBlazor.Localization.Json
public JsonStringLocalizerFactory(IOptions<JsonLocalizationOptions> localizationOptions, ILoggerFactory loggerFactory)
{
_resourcesRelativePath = localizationOptions.Value.ResourcesPath;
_resourcesType = localizationOptions.Value.ResourcesType;
_loggerFactory = loggerFactory;
}
@@ -35,15 +32,8 @@ namespace BootstrapBlazor.Localization.Json
/// <returns></returns>
public IStringLocalizer Create(Type resourceSource)
{
if (resourceSource.Name == "Controller")
{
return CreateJsonStringLocalizer(Path.Combine(PathHelpers.GetApplicationRoot(), GetResourcePath(resourceSource.Assembly)), TryFixInnerClassPath("Controller"));
}
var typeInfo = resourceSource.GetTypeInfo();
var assembly = typeInfo.Assembly;
var assemblyName = resourceSource.Assembly.GetName().Name;
var resourcesPath = Path.Combine(PathHelpers.GetApplicationRoot(), GetResourcePath(assembly));
var typeName = $"{assemblyName}.{typeInfo.Name}" == typeInfo.FullName
? typeInfo.Name
: typeInfo.FullName.Substring(assemblyName.Length + 1);
@@ -56,7 +46,7 @@ namespace BootstrapBlazor.Localization.Json
typeName = TryFixInnerClassPath(typeName);
return CreateJsonStringLocalizer(resourcesPath, typeName);
return CreateJsonStringLocalizer(typeInfo.Assembly, typeName, $"{assemblyName}.{_resourcesRelativePath}");
}
/// <summary>
@@ -71,50 +61,23 @@ namespace BootstrapBlazor.Localization.Json
var assemblyName = new AssemblyName(location);
var assembly = Assembly.Load(assemblyName);
var resourcesPath = Path.Combine(PathHelpers.GetApplicationRoot(), GetResourcePath(assembly));
string? resourceName = null;
if (_resourcesType == ResourcesType.TypeBased)
{
resourceName = TrimPrefix(baseName, location + ".");
}
return CreateJsonStringLocalizer(resourcesPath, resourceName);
return CreateJsonStringLocalizer(assembly, string.Empty, resourceName);
}
/// <summary>
/// 创建 IStringLocalizer 实例方法
/// </summary>
/// <param name="resourcesPath"></param>
/// <param name="resourcename"></param>
/// <param name="assembly"></param>
/// <param name="typeName"></param>
/// <param name="resourceName"></param>
/// <returns></returns>
protected virtual IStringLocalizer CreateJsonStringLocalizer(string resourcesPath, string? resourcename)
protected virtual IStringLocalizer CreateJsonStringLocalizer(Assembly assembly, string typeName, string? resourceName)
{
var logger = _loggerFactory.CreateLogger<JsonStringLocalizer>();
return new JsonStringLocalizer(
resourcesPath,
_resourcesType == ResourcesType.TypeBased ? resourcename : null,
logger);
}
private string GetResourcePath(Assembly assembly)
{
var resourceLocationAttribute = assembly.GetCustomAttribute<ResourceLocationAttribute>();
return resourceLocationAttribute == null
? _resourcesRelativePath
: resourceLocationAttribute.ResourceLocation;
}
private static string TrimPrefix(string name, string prefix)
{
if (name.StartsWith(prefix, StringComparison.Ordinal))
{
return name.Substring(prefix.Length);
}
return name;
return new JsonStringLocalizer(assembly, resourceName, typeName, logger);
}
private string TryFixInnerClassPath(string path)

View File

@@ -1,18 +0,0 @@
namespace BootstrapBlazor.Localization.Json
{
/// <summary>
/// 资源类型枚举
/// </summary>
public enum ResourcesType
{
/// <summary>
/// 基于语言文化
/// </summary>
CultureBased,
/// <summary>
/// 基于类型
/// </summary>
TypeBased
}
}

View File

@@ -1,4 +0,0 @@
{
"NoDataTip": "No Data",
"PlaceHolder": "Please Input"
}

View File

@@ -1,4 +0,0 @@
{
"NoDataTip": "无匹配数据",
"PlaceHolder": "请输入"
}

View File

@@ -1,7 +0,0 @@
{
"HeaderText": "Captcha",
"BarText": "Slide right to fill puzzle",
"FailedText": "Load failed",
"LoadText": "Loading ...",
"TryText": "Try again"
}

View File

@@ -1,7 +0,0 @@
{
"HeaderText": "请完成安全验证",
"BarText": "向右滑动填充拼图",
"FailedText": "加载失败",
"LoadText": "正在加载 ...",
"TryText": "再试一次"
}

View File

@@ -1,6 +0,0 @@
{
"HeaderText": "Monitor",
"LightTitle": "Light",
"ClearButtonText": "Clear",
"AutoScrollText": "AutoScroll"
}

View File

@@ -1,6 +0,0 @@
{
"HeaderText": "系统监控",
"LightTitle": "通讯指示灯",
"ClearButtonText": "清屏",
"AutoScrollText": "自动滚屏"
}

View File

@@ -1,24 +0,0 @@
{
"DatePlaceHolder": "Date",
"TimePlaceHolder": "Time",
"DateTimePlaceHolderText": "Please select ...",
"DatePlaceHolderText": "Please select ...",
"TimeFormat": "hh\\:mm\\:ss",
"DateFormat": "M/d/yyyy",
"DateTimeFormat": "M/d/yyyy hh\\:mm\\:ss",
"AiraPrevYearLabel": "Prev Year",
"AiraNextYearLabel": "Next Year",
"AiraPrevMonthLabel": "Prev Month",
"AiraNextMonthLabel": "Next Month",
"ClearButtonText": "Clear",
"NowButtonText": "Now",
"ConfirmButtonText": "Ok",
"CancelButtonText": "Cancel",
"YearText": "{0}",
"MonthText": "{0}",
"YearPeriodText": "{0} - {1}",
"Months": "January,February,March,April,May,June,July,August,September,October,November,December",
"MonthLists": "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
"WeekLists": "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
"GenericTypeErroMessage": "DateTimePicker only support DateTime or Nullable<DateTime>"
}

View File

@@ -1,24 +0,0 @@
{
"DatePlaceHolder": "选择日期",
"TimePlaceHolder": "选择时间",
"DateTimePlaceHolderText": "请选择日期时间",
"DatePlaceHolderText": "请选择日期",
"TimeFormat": "hh\\:mm\\:ss",
"DateFormat": "yyyy-MM-dd",
"DateTimeFormat": "yyyy-MM-dd hh\\:mm\\:ss",
"AiraPrevYearLabel": "前一年",
"AiraNextYearLabel": "后一年",
"AiraPrevMonthLabel": "上一月",
"AiraNextMonthLabel": "下一月",
"ClearButtonText": "清空",
"NowButtonText": "此刻",
"ConfirmButtonText": "确定",
"CancelButtonText": "取消",
"YearText": "{0} 年",
"MonthText": "{0} 月",
"YearPeriodText": "{0} 年 - {1} 年",
"Months": "1,2,3,4,5,6,7,8,9,10,11,12",
"MonthLists": "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月",
"WeekLists": "日,一,二,三,四,五,六",
"GenericTypeErroMessage": "DateTimePicker 组件仅支持绑定泛型为 DateTime 或者 DateTime?"
}

View File

@@ -1,3 +0,0 @@
{
"PlaceHolder":"Please select ..."
}

View File

@@ -1,3 +0,0 @@
{
"PlaceHolder":"请选择 ..."
}

View File

@@ -1,3 +0,0 @@
{
"PlaceHolder":"Click to edit"
}

View File

@@ -1,3 +0,0 @@
{
"PlaceHolder":"点击后编辑"
}

View File

@@ -1,4 +0,0 @@
{
"ModelInvalidOperationExceptionMessage": "ValidateForm MODEL not match {0} MODEL",
"PlaceHolderText":"Please input ..."
}

View File

@@ -1,4 +0,0 @@
{
"ModelInvalidOperationExceptionMessage": "验证表单与 {0} 绑定模型不一致",
"PlaceHolderText":"请输入 ..."
}

View File

@@ -1,3 +0,0 @@
{
"ErrorMessage": "Please enter the same value again"
}

View File

@@ -1,3 +0,0 @@
{
"ErrorMessage": "你的输入不相同"
}

View File

@@ -1,3 +0,0 @@
{
"TooltipText": "Go top"
}

View File

@@ -1,3 +0,0 @@
{
"TooltipText": "返回顶端"
}

View File

@@ -1,3 +0,0 @@
{
"TooltipText": "Click to Expand/Collapse sidebar"
}

View File

@@ -1,3 +0,0 @@
{
"TooltipText": "点击展开收缩左侧菜单"
}

View File

@@ -1,6 +0,0 @@
{
"PlaceHolder": "Click for select ...",
"SelectAllText": "All",
"ReverseSelectText": "Reverse",
"ClearText": "Clear"
}

View File

@@ -1,6 +0,0 @@
{
"PlaceHolder": "点击进行多选 ...",
"SelectAllText": "全选",
"ReverseSelectText": "反选",
"ClearText": "清除"
}

View File

@@ -1,12 +0,0 @@
{
"AiraPageLabel": "Pagination",
"AiraPrevPageText": "Prev",
"AiraFirstPageText": "First",
"AiraNextPageText": "Next",
"PageInfoText": "{0}-{1}",
"TotalInfoText": ", Total {0}",
"PrePageInfoText": "",
"RowInfoText": "",
"LabelString": "page {0}",
"SelectItemsText": "{0} /page"
}

View File

@@ -1,12 +0,0 @@
{
"AiraPageLabel": "分页组件",
"AiraPrevPageText": "上一页",
"AiraFirstPageText": "第一页",
"AiraNextPageText": "下一页",
"PageInfoText": "显示第 {0} 到第 {1} 条记录",
"TotalInfoText": ",总共 {0} 条记录",
"PrePageInfoText": "每页显示",
"RowInfoText": "条记录",
"LabelString": "第 {0} 页",
"SelectItemsText": "{0} 条/页"
}

View File

@@ -1,5 +0,0 @@
{
"CloseButtonText": "Cancel",
"ConfirmButtonText": "Ok",
"Content": "Are you sure DELETE?"
}

View File

@@ -1,5 +0,0 @@
{
"CloseButtonText": "关闭",
"ConfirmButtonText": "确定",
"Content": "确认删除吗?"
}

View File

@@ -1,5 +0,0 @@
{
"PlaceHolder": "Please input ...",
"ClearButtonText": "Clear",
"GenerateButtonText": "Generate"
}

View File

@@ -1,5 +0,0 @@
{
"PlaceHolder": "请输入内容",
"ClearButtonText": "清除",
"GenerateButtonText": "生成"
}

View File

@@ -1,3 +0,0 @@
{
"ErrorMessage": "This field is required"
}

View File

@@ -1,3 +0,0 @@
{
"ErrorMessage": "这是必填字段"
}

View File

@@ -1,3 +0,0 @@
{
"SearchButtonText": "Search"
}

View File

@@ -1,3 +0,0 @@
{
"SearchButtonText": "搜索"
}

View File

@@ -1,3 +0,0 @@
{
"PlaceHolder": "Click for select ..."
}

View File

@@ -1,3 +0,0 @@
{
"PlaceHolder": "请选择 ..."
}

View File

@@ -1,3 +0,0 @@
{
"ErrorMessage": "Please enter a value less than or equal to {{0}}"
}

View File

@@ -1,3 +0,0 @@
{
"ErrorMessage": "最多可以输入 {{0}} 个字符"
}

View File

@@ -1,5 +0,0 @@
{
"CloseButtonText": "Close",
"CancelButtonText": "Cancel",
"ConfirmButtonText": "Confirm"
}

View File

@@ -1,5 +0,0 @@
{
"CloseButtonText": "关闭",
"CancelButtonText": "取消",
"ConfirmButtonText": "确认"
}

View File

@@ -1,4 +0,0 @@
{
"OnInnerText": "On",
"OffInnerText": "Off"
}

View File

@@ -1,4 +0,0 @@
{
"OnInnerText": "开",
"OffInnerText": "关"
}

View File

@@ -1,36 +0,0 @@
{
"AddButtonText": "Add",
"EditButtonText": "Edit",
"DeleteButtonText": "Delete",
"CancelDeleteButtonText": "Cancel",
"ConfirmDeleteButtonText": "Delete",
"ConfirmDeleteContentText": "Are you sure to DELETE all selected rows?",
"RefreshButtonText": "Refresh",
"ColumnButtonTitleText": "Show/Hide Columns",
"ColumnButtonText": "Columns",
"ExportButtonText": "Export",
"SearchPlaceholderText": "Search",
"SearchButtonText": "Search",
"ResetButtonText": "Reset",
"AdvanceButtonText": "Advance Search",
"CheckboxDisplayText": "All",
"EditModalTitle": "Edit Data",
"AddModalTitle": "New Data",
"ColumnButtonTemplateHeaderText": "",
"SearchTooltip": "<div class='search-input-tooltip'>Please input ...</br><kbd>Enter</kbd> Search <kbd>ESC</kbd> Clear</div>",
"SearchModalTitle": "Searching",
"AddButtonToastTitle": "Add Data",
"AddButtonToastContent": "Save data failed. Please provider SAVE method",
"EditButtonToastTitle": "Add Data",
"EditButtonToastNotSelectContent": "Save data failed. Please provider SAVE method",
"EditButtonToastMoreSelectContent": "Only one row can be EDIT",
"EditButtonToastNoSaveMethodContent": "Can not EDIT data. Please provider SAVE method",
"SaveButtonToastTitle": "Save Data",
"SaveButtonToastContent": "Save data failed. Please provider SAVE method",
"SaveButtonToastResultContent": "Save data {0}, auto close after {1}s",
"SuccessText": "Successful",
"FailText": "Failed",
"DeleteButtonToastTitle": "Delete Data",
"DeleteButtonToastContent": "Please select DELETE rows, auto claose after {1}s",
"DeleteButtonToastResultContent": "Delete data {0}, auto claose after {1}s"
}

View File

@@ -1,36 +0,0 @@
{
"AddButtonText": "新建",
"EditButtonText": "编辑",
"DeleteButtonText": "删除",
"CancelDeleteButtonText": "取消",
"ConfirmDeleteButtonText": "删除",
"ConfirmDeleteContentText": "确认要删除选中的所有行吗?",
"RefreshButtonText": "刷新",
"ColumnButtonTitleText": "列显示隐藏控制",
"ColumnButtonText": "列",
"ExportButtonText": "导出数据",
"SearchPlaceholderText": "搜索",
"SearchButtonText": "搜索",
"ResetButtonText": "清空过滤",
"AdvanceButtonText": "高级搜索",
"CheckboxDisplayText": "选择",
"EditModalTitle": "编辑数据窗口",
"AddModalTitle": "新建数据窗口",
"ColumnButtonTemplateHeaderText": "操作",
"SearchTooltip": "<div class='search-input-tooltip'>输入任意字符串全局搜索</br><kbd>Enter</kbd> 搜索 <kbd>ESC</kbd> 清除搜索</div>",
"SearchModalTitle": "搜索条件",
"AddButtonToastTitle": "新建数据",
"AddButtonToastContent": "未提供保存数据方法,无法新建数据",
"EditButtonToastTitle": "编辑数据",
"EditButtonToastNotSelectContent": "请选择要编辑的数据",
"EditButtonToastMoreSelectContent": "只能选择一项要编辑的数据",
"EditButtonToastNoSaveMethodContent": "未提供保存数据方法,无法编辑数据",
"SaveButtonToastTitle": "保存数据",
"SaveButtonToastContent": "未提供保存数据方法,无法保存数据",
"SaveButtonToastResultContent": "保存数据{0}, {1} 秒后自动关闭",
"SuccessText": "成功",
"FailText": "失败",
"DeleteButtonToastTitle": "删除数据",
"DeleteButtonToastContent": "请选择要删除的数据, {0} 秒后自动关闭",
"DeleteButtonToastResultContent": "删除数据{0}, {1} 秒后自动关闭"
}

View File

@@ -1,4 +0,0 @@
{
"CloseButtonText": "Close",
"SaveButtonText": "Save"
}

View File

@@ -1,4 +0,0 @@
{
"CloseButtonText": "关闭",
"SaveButtonText": "保存"
}

View File

@@ -1,17 +0,0 @@
{
"Title": "Filter",
"ClearButtonText": "Clear",
"FilterButtonText": "Filter",
"BoolFilter.AllText": "All",
"BoolFilter.TrueText": "True",
"BoolFilter.FalseText": "False",
"GreaterThanOrEqual": "GreaterThanOrEqual",
"LessThanOrEqual": "LessThanOrEqual",
"GreaterThan": "GreaterThan",
"LessThan": "LessThan",
"Equal": "Equal",
"NotEqual": "NotEqual",
"Contains": "Contains",
"NotContains": "NotContains",
"EnumFilter.AllText": "All"
}

View File

@@ -1,17 +0,0 @@
{
"Title": "过滤",
"ClearButtonText": "重置",
"FilterButtonText": "确认",
"BoolFilter.AllText": "全部",
"BoolFilter.TrueText": "选中",
"BoolFilter.FalseText": "未选中",
"GreaterThanOrEqual": "大于等于",
"LessThanOrEqual": "小于等于",
"GreaterThan": "大于",
"LessThan": "小于",
"Equal": "等于",
"NotEqual": "不等于",
"Contains": "包含",
"NotContains": "不包含",
"EnumFilter.AllText": "全选"
}

View File

@@ -1,4 +0,0 @@
{
"ResetButtonText": "Reset",
"QueryButtonText": "Query"
}

View File

@@ -1,4 +0,0 @@
{
"ResetButtonText": "重置",
"QueryButtonText": "查询"
}

View File

@@ -1,6 +0,0 @@
{
"PauseText": "Pause",
"ResumeText": "Resume",
"CancelText": "Cancel",
"StarText": "Star"
}

View File

@@ -1,6 +0,0 @@
{
"PauseText": "暂停",
"ResumeText": "继续",
"CancelText": "取消",
"StarText": "开始计时"
}

View File

@@ -1,4 +0,0 @@
{
"OnText": "Expand",
"OffText": "Collapse"
}

View File

@@ -1,4 +0,0 @@
{
"OnText": "展开",
"OffText": "收缩"
}

View File

@@ -1,4 +0,0 @@
{
"LeftPanelText": "List 1",
"RightPanelText": "List 2"
}

View File

@@ -1,4 +0,0 @@
{
"LeftPanelText": "列表 1",
"RightPanelText": "列表 2"
}

View File

@@ -1,6 +0,0 @@
{
"Text": "Upload",
"ResetText": "Reset",
"AllowFileTypeErrorMessage": "Only allow Images type file",
"FileTooLargeText": "File to large"
}

View File

@@ -1,6 +0,0 @@
{
"Text": "点击上传",
"ResetText": "重置",
"AllowFileTypeErrorMessage": "只允许选择图片类型文件",
"FileTooLargeText": "文件太大"
}

View File

@@ -55,6 +55,15 @@
language: (navigator.browserLanguage || navigator.language).toLowerCase()
};
$.blazorCulture = {
get: () => {
return window.localStorage['BlazorCulture'];
},
set: (value) => {
window.localStorage['BlazorCulture'] = value;
}
};
$.generatefile = (fileName, bytesBase64, contenttype) => {
var link = document.createElement('a');
link.download = fileName;

View File

@@ -6,10 +6,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>