diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index c3e00e3aa..21536b9a5 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -290,7 +290,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Location.Tencen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Location.Baidu.Tests", "tests\LINGYUN.Abp.Location.Baidu.Tests\LINGYUN.Abp.Location.Baidu.Tests.csproj", "{C892CD81-50AE-49E5-BF44-A0C28A1614CC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.AspNetCore.Mvc.Client", "modules\common\LINGYUN.Abp.AspNetCore.Mvc.Client\LINGYUN.Abp.AspNetCore.Mvc.Client.csproj", "{EEF03CC6-1013-4AAF-BEED-BB4BA5021039}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc.Client", "modules\common\LINGYUN.Abp.AspNetCore.Mvc.Client\LINGYUN.Abp.AspNetCore.Mvc.Client.csproj", "{EEF03CC6-1013-4AAF-BEED-BB4BA5021039}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "localization", "localization", "{90E88EAC-4291-4406-8D88-EFDF61B11292}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Xml", "modules\localization\LINGYUN.Abp.Localization.Xml\LINGYUN.Abp.Localization.Xml.csproj", "{84868710-ECBB-4025-900A-EEB99EC49534}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Xml.Tests", "tests\LINGYUN.Abp.Localization.Xml.Tests\LINGYUN.Abp.Localization.Xml.Tests.csproj", "{A061D2B4-B650-4F7F-A6CB-5C8FFFD512ED}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Json", "modules\localization\LINGYUN.Abp.Localization.Json\LINGYUN.Abp.Localization.Json.csproj", "{EA563F48-A6EF-4886-B607-2A83F7795F1B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Localization.Json.Tests", "tests\LINGYUN.Abp.Localization.Json.Tests\LINGYUN.Abp.Localization.Json.Tests.csproj", "{EBCF7D88-49E2-413D-A7A6-1A76BC2E8161}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -770,6 +780,22 @@ Global {EEF03CC6-1013-4AAF-BEED-BB4BA5021039}.Debug|Any CPU.Build.0 = Debug|Any CPU {EEF03CC6-1013-4AAF-BEED-BB4BA5021039}.Release|Any CPU.ActiveCfg = Release|Any CPU {EEF03CC6-1013-4AAF-BEED-BB4BA5021039}.Release|Any CPU.Build.0 = Release|Any CPU + {84868710-ECBB-4025-900A-EEB99EC49534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84868710-ECBB-4025-900A-EEB99EC49534}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84868710-ECBB-4025-900A-EEB99EC49534}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84868710-ECBB-4025-900A-EEB99EC49534}.Release|Any CPU.Build.0 = Release|Any CPU + {A061D2B4-B650-4F7F-A6CB-5C8FFFD512ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A061D2B4-B650-4F7F-A6CB-5C8FFFD512ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A061D2B4-B650-4F7F-A6CB-5C8FFFD512ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A061D2B4-B650-4F7F-A6CB-5C8FFFD512ED}.Release|Any CPU.Build.0 = Release|Any CPU + {EA563F48-A6EF-4886-B607-2A83F7795F1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA563F48-A6EF-4886-B607-2A83F7795F1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA563F48-A6EF-4886-B607-2A83F7795F1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA563F48-A6EF-4886-B607-2A83F7795F1B}.Release|Any CPU.Build.0 = Release|Any CPU + {EBCF7D88-49E2-413D-A7A6-1A76BC2E8161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBCF7D88-49E2-413D-A7A6-1A76BC2E8161}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBCF7D88-49E2-413D-A7A6-1A76BC2E8161}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBCF7D88-49E2-413D-A7A6-1A76BC2E8161}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -915,6 +941,11 @@ Global {94B47385-E47F-4FD7-A3A9-A7AA122EFC93} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F} {C892CD81-50AE-49E5-BF44-A0C28A1614CC} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F} {EEF03CC6-1013-4AAF-BEED-BB4BA5021039} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E} + {90E88EAC-4291-4406-8D88-EFDF61B11292} = {C5CAD011-DF84-4914-939C-0C029DCEF26F} + {84868710-ECBB-4025-900A-EEB99EC49534} = {90E88EAC-4291-4406-8D88-EFDF61B11292} + {A061D2B4-B650-4F7F-A6CB-5C8FFFD512ED} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F} + {EA563F48-A6EF-4886-B607-2A83F7795F1B} = {90E88EAC-4291-4406-8D88-EFDF61B11292} + {EBCF7D88-49E2-413D-A7A6-1A76BC2E8161} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index f106c402f..3e8024941 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/aspnet-core/LINGYUN.MicroService.Common.sln @@ -117,6 +117,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Location.Tencen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc.Client", "modules\common\LINGYUN.Abp.AspNetCore.Mvc.Client\LINGYUN.Abp.AspNetCore.Mvc.Client.csproj", "{7F767ACF-754A-4EBC-8936-3C1402B6EF82}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "localization", "localization", "{E73A0F8B-2B4B-4CED-82A4-1EE5E0B89744}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Json", "modules\localization\LINGYUN.Abp.Localization.Json\LINGYUN.Abp.Localization.Json.csproj", "{DADD5D6E-F09A-4563-A659-7922E26C40AB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Xml", "modules\localization\LINGYUN.Abp.Localization.Xml\LINGYUN.Abp.Localization.Xml.csproj", "{8CC72F4E-F134-4A43-9037-5D4D1F29B68A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Xml.Tests", "tests\LINGYUN.Abp.Localization.Xml.Tests\LINGYUN.Abp.Localization.Xml.Tests.csproj", "{94FEA59E-3B6D-41A0-9E44-BA5D6477244F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Json.Tests", "tests\LINGYUN.Abp.Localization.Json.Tests\LINGYUN.Abp.Localization.Json.Tests.csproj", "{BA2F4EC9-BC2C-482A-9123-BDACB8B15295}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -307,6 +317,22 @@ Global {7F767ACF-754A-4EBC-8936-3C1402B6EF82}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F767ACF-754A-4EBC-8936-3C1402B6EF82}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F767ACF-754A-4EBC-8936-3C1402B6EF82}.Release|Any CPU.Build.0 = Release|Any CPU + {DADD5D6E-F09A-4563-A659-7922E26C40AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DADD5D6E-F09A-4563-A659-7922E26C40AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DADD5D6E-F09A-4563-A659-7922E26C40AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DADD5D6E-F09A-4563-A659-7922E26C40AB}.Release|Any CPU.Build.0 = Release|Any CPU + {8CC72F4E-F134-4A43-9037-5D4D1F29B68A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CC72F4E-F134-4A43-9037-5D4D1F29B68A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CC72F4E-F134-4A43-9037-5D4D1F29B68A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CC72F4E-F134-4A43-9037-5D4D1F29B68A}.Release|Any CPU.Build.0 = Release|Any CPU + {94FEA59E-3B6D-41A0-9E44-BA5D6477244F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94FEA59E-3B6D-41A0-9E44-BA5D6477244F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94FEA59E-3B6D-41A0-9E44-BA5D6477244F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94FEA59E-3B6D-41A0-9E44-BA5D6477244F}.Release|Any CPU.Build.0 = Release|Any CPU + {BA2F4EC9-BC2C-482A-9123-BDACB8B15295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA2F4EC9-BC2C-482A-9123-BDACB8B15295}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA2F4EC9-BC2C-482A-9123-BDACB8B15295}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA2F4EC9-BC2C-482A-9123-BDACB8B15295}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -367,6 +393,11 @@ Global {221725FF-6C01-4F41-9F29-AC04C7D52611} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} {1B494EA1-28CF-4A61-B0BE-70BBA425C316} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} {7F767ACF-754A-4EBC-8936-3C1402B6EF82} = {086BE5BE-8594-4DA7-8819-935FEF76DABD} + {E73A0F8B-2B4B-4CED-82A4-1EE5E0B89744} = {02EA4E78-5891-43BC-944F-3E52FEE032E4} + {DADD5D6E-F09A-4563-A659-7922E26C40AB} = {E73A0F8B-2B4B-4CED-82A4-1EE5E0B89744} + {8CC72F4E-F134-4A43-9037-5D4D1F29B68A} = {E73A0F8B-2B4B-4CED-82A4-1EE5E0B89744} + {94FEA59E-3B6D-41A0-9E44-BA5D6477244F} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} + {BA2F4EC9-BC2C-482A-9123-BDACB8B15295} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN.Abp.Localization.Json.csproj b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN.Abp.Localization.Json.csproj new file mode 100644 index 000000000..76ab204bf --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN.Abp.Localization.Json.csproj @@ -0,0 +1,15 @@ + + + + + + netstandard2.0 + + + + + + + + + diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonModule.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonModule.cs new file mode 100644 index 000000000..b2a4672cc --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Localization.Json +{ + [DependsOn(typeof(AbpLocalizationModule))] + public class AbpLocalizationJsonModule : AbpModule + { + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/JsonPhysicalFileLocalizationResourceContributor.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/JsonPhysicalFileLocalizationResourceContributor.cs new file mode 100644 index 000000000..1b862f4b8 --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/JsonPhysicalFileLocalizationResourceContributor.cs @@ -0,0 +1,116 @@ +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.Internal; +using Volo.Abp.Localization; +using Volo.Abp.Localization.Json; + +namespace LINGYUN.Abp.Localization.Json +{ + public class JsonPhysicalFileLocalizationResourceContributor : ILocalizationResourceContributor + { + private readonly string _filePath; + + private IFileProvider _fileProvider; + private Dictionary _dictionaries; + private bool _subscribedForChanges; + private readonly object _syncObj = new object(); + + public JsonPhysicalFileLocalizationResourceContributor(string filePath) + { + _filePath = filePath; + } + + public void Initialize(LocalizationResourceInitializationContext context) + { + _fileProvider = new PhysicalFileProvider(_filePath); + } + + public LocalizedString GetOrNull(string cultureName, string name) + { + return GetDictionaries().GetOrDefault(cultureName)?.GetOrNull(name); + } + + public void Fill(string cultureName, Dictionary dictionary) + { + GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary); + } + + protected virtual Dictionary GetDictionaries() + { + var dictionaries = _dictionaries; + if (dictionaries != null) + { + return dictionaries; + } + + lock (_syncObj) + { + dictionaries = _dictionaries; + if (dictionaries != null) + { + return dictionaries; + } + + if (!_subscribedForChanges) + { + ChangeToken.OnChange(() => _fileProvider.Watch(_filePath.EnsureEndsWith('/') + "*.*"), + () => + { + _dictionaries = null; + }); + + _subscribedForChanges = true; + } + + dictionaries = _dictionaries = CreateDictionaries(); + } + + return dictionaries; + } + + protected virtual Dictionary CreateDictionaries() + { + var dictionaries = new Dictionary(); + + foreach (var file in _fileProvider.GetDirectoryContents(string.Empty)) + { + if (file.IsDirectory || !CanParseFile(file)) + { + continue; + } + + var dictionary = CreateDictionaryFromFile(file); + if (dictionaries.ContainsKey(dictionary.CultureName)) + { + throw new AbpException($"{file.GetVirtualOrPhysicalPathOrNull()} dictionary has a culture name '{dictionary.CultureName}' which is already defined!"); + } + + dictionaries[dictionary.CultureName] = dictionary; + } + + return dictionaries; + } + + protected virtual bool CanParseFile(IFileInfo file) + { + return file.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase); + } + + protected virtual ILocalizationDictionary CreateDictionaryFromFile(IFileInfo file) + { + using (var stream = file.CreateReadStream()) + { + return CreateDictionaryFromFileContent(Utf8Helper.ReadStringFromStream(stream)); + } + } + + protected virtual ILocalizationDictionary CreateDictionaryFromFileContent(string fileContent) + { + return JsonLocalizationDictionaryBuilder.BuildFromJsonString(fileContent); + } + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/LocalizationResourceExtensions.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/LocalizationResourceExtensions.cs new file mode 100644 index 000000000..a662d5dcf --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/LINGYUN/Abp/Localization/Json/LocalizationResourceExtensions.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Localization.Json +{ + public static class LocalizationResourceExtensions + { + /// + /// 添加Json本地磁盘文件支持 + /// + /// + /// + /// + public static LocalizationResource AddPhysicalJson( + [NotNull] this LocalizationResource localizationResource, + [NotNull] string jsonFilePath) + { + Check.NotNull(localizationResource, nameof(localizationResource)); + Check.NotNull(jsonFilePath, nameof(jsonFilePath)); + + localizationResource.Contributors.Add(new JsonPhysicalFileLocalizationResourceContributor(jsonFilePath)); + + return localizationResource; + } + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/README.md b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/README.md new file mode 100644 index 000000000..038608d48 --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Json/README.md @@ -0,0 +1,55 @@ +# LINGYUN.Abp.Localization.Json + +## 模块说明 + +本地化组件的Json本地文件系统集成,Abp内置组件仅集成了基于IVirtualFileProvider的实现 + +此组件基于PhysicalFileProvider + +### 基础模块 + +### 高阶模块 + +### 权限定义 + +### 功能定义 + +### 配置定义 + +### 如何使用 + + +```csharp + + [DependsOn( + typeof(AbpLocalizationJsonModule))] + public class YouProjectModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Add("en") + // 一般配置在宿主项目中, 写入宿主项目中存储json文件的绝对路径(受PhysicalFileProvider的限制) + // json文件格式与Abp默认json格式保持一致 + // 详情见: https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.fileproviders.physicalfileprovider?view=dotnet-plat-ext-5.0 + .AddPhysicalJson(Path.Combine(Directory.GetCurrentDirectory(), "Resources")); + }); + } + } + +``` + +```json + +{ + "culture": "en", + "texts": { + "Hello China": "Hello China!" + } +} + +``` + +### 更新日志 diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN.Abp.Localization.Xml.csproj b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN.Abp.Localization.Xml.csproj new file mode 100644 index 000000000..da94801ce --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN.Abp.Localization.Xml.csproj @@ -0,0 +1,15 @@ + + + + + + netstandard2.0 + + + + + + + + + diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlModule.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlModule.cs new file mode 100644 index 000000000..60d9bd316 --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Localization.Xml +{ + [DependsOn(typeof(AbpLocalizationModule))] + public class AbpLocalizationXmlModule : AbpModule + { + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/LocalizationResourceExtensions.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/LocalizationResourceExtensions.cs new file mode 100644 index 000000000..d6b90bfe9 --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/LocalizationResourceExtensions.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using System; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Localization.Xml +{ + public static class LocalizationResourceExtensions + { + /// + /// 添加Xml虚拟文件系统支持 + /// + /// + /// + /// + public static LocalizationResource AddVirtualXml( + [NotNull] this LocalizationResource localizationResource, + [NotNull] string virtualPath) + { + Check.NotNull(localizationResource, nameof(localizationResource)); + Check.NotNull(virtualPath, nameof(virtualPath)); + + localizationResource.Contributors.Add(new XmlVirtualFileLocalizationResourceContributor( + virtualPath.EnsureStartsWith('/') + )); + + return localizationResource; + } + /// + /// 添加Xml本地磁盘文件支持 + /// + /// + /// + /// + public static LocalizationResource AddPhysicalXml( + [NotNull] this LocalizationResource localizationResource, + [NotNull] string xmlFilePath) + { + Check.NotNull(localizationResource, nameof(localizationResource)); + Check.NotNull(xmlFilePath, nameof(xmlFilePath)); + + localizationResource.Contributors.Add(new XmlPhysicalFileLocalizationResourceContributor(xmlFilePath)); + + return localizationResource; + } + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlFileLocalizationResourceContributorBase.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlFileLocalizationResourceContributorBase.cs new file mode 100644 index 000000000..e49f42e4b --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlFileLocalizationResourceContributorBase.cs @@ -0,0 +1,119 @@ +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.Internal; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Localization.Xml +{ + public abstract class XmlFileLocalizationResourceContributorBase : ILocalizationResourceContributor + { + private readonly string _filePath; + + private IFileProvider _fileProvider; + private Dictionary _dictionaries; + private bool _subscribedForChanges; + private readonly object _syncObj = new object(); + + protected XmlFileLocalizationResourceContributorBase(string filePath) + { + _filePath = filePath; + } + + public virtual void Initialize(LocalizationResourceInitializationContext context) + { + _fileProvider = BuildFileProvider(context); + + Check.NotNull(_fileProvider, nameof(_fileProvider)); + } + + public virtual LocalizedString GetOrNull(string cultureName, string name) + { + return GetDictionaries().GetOrDefault(cultureName)?.GetOrNull(name); + } + + public virtual void Fill(string cultureName, Dictionary dictionary) + { + GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary); + } + + protected virtual Dictionary GetDictionaries() + { + var dictionaries = _dictionaries; + if (dictionaries != null) + { + return dictionaries; + } + + lock (_syncObj) + { + dictionaries = _dictionaries; + if (dictionaries != null) + { + return dictionaries; + } + + if (!_subscribedForChanges) + { + ChangeToken.OnChange(() => _fileProvider.Watch(_filePath.EnsureEndsWith('/') + "*.*"), + () => + { + _dictionaries = null; + }); + + _subscribedForChanges = true; + } + + dictionaries = _dictionaries = CreateDictionaries(_fileProvider, _filePath); + } + + return dictionaries; + } + + protected virtual Dictionary CreateDictionaries(IFileProvider fileProvider, string filePath) + { + var dictionaries = new Dictionary(); + + foreach (var file in fileProvider.GetDirectoryContents(filePath)) + { + if (file.IsDirectory || !CanParseFile(file)) + { + continue; + } + + var dictionary = CreateDictionaryFromFile(file); + if (dictionaries.ContainsKey(dictionary.CultureName)) + { + throw new AbpException($"{file.GetVirtualOrPhysicalPathOrNull()} dictionary has a culture name '{dictionary.CultureName}' which is already defined!"); + } + + dictionaries[dictionary.CultureName] = dictionary; + } + + return dictionaries; + } + + protected virtual bool CanParseFile(IFileInfo file) + { + return file.Name.EndsWith(".xml", StringComparison.OrdinalIgnoreCase); + } + + protected virtual ILocalizationDictionary CreateDictionaryFromFile(IFileInfo file) + { + using (var stream = file.CreateReadStream()) + { + return CreateDictionaryFromFileContent(Utf8Helper.ReadStringFromStream(stream)); + } + } + + protected virtual ILocalizationDictionary CreateDictionaryFromFileContent(string fileContent) + { + return XmlLocalizationDictionaryBuilder.BuildFromXmlString(fileContent); + } + + protected abstract IFileProvider BuildFileProvider(LocalizationResourceInitializationContext context); + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlLocalizationDictionaryBuilder.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlLocalizationDictionaryBuilder.cs new file mode 100644 index 000000000..f3dbb712e --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlLocalizationDictionaryBuilder.cs @@ -0,0 +1,74 @@ +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Serialization; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Localization.Xml +{ + public static class XmlLocalizationDictionaryBuilder + { + public static ILocalizationDictionary BuildFromFile(string filePath) + { + try + { + return BuildFromXmlString(File.ReadAllText(filePath)); + } + catch (Exception ex) + { + throw new AbpException("Invalid localization file format: " + filePath, ex); + } + } + + public static ILocalizationDictionary BuildFromXmlString(string xmlString) + { + XmlLocalizationFile xmlFile; + try + { + XmlSerializer serializer = new XmlSerializer(typeof(XmlLocalizationFile)); + using (StringReader reader = new StringReader(xmlString)) + { + xmlFile = (XmlLocalizationFile)serializer.Deserialize(reader); + } + } + catch (Exception ex) + { + throw new AbpException("Can not parse xml string. " + ex.Message); + } + + var cultureCode = xmlFile.Culture.Name; + if (string.IsNullOrEmpty(cultureCode)) + { + throw new AbpException("Culture is empty in language json file."); + } + + var dictionary = new Dictionary(); + var dublicateNames = new List(); + foreach (var item in xmlFile.Texts) + { + if (string.IsNullOrEmpty(item.Key)) + { + throw new AbpException("The key is empty in given json string."); + } + + if (dictionary.GetOrDefault(item.Key) != null) + { + dublicateNames.Add(item.Key); + } + + dictionary[item.Key] = new LocalizedString(item.Key, item.Value.NormalizeLineEndings()); + } + + if (dublicateNames.Count > 0) + { + throw new AbpException( + "A dictionary can not contain same key twice. There are some duplicated names: " + + dublicateNames.JoinAsString(", ")); + } + + return new StaticLocalizationDictionary(cultureCode, dictionary); + } + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlLocalizationFile.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlLocalizationFile.cs new file mode 100644 index 000000000..b2f93373e --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlLocalizationFile.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +namespace LINGYUN.Abp.Localization.Xml +{ + [Serializable] + [XmlRoot(Namespace = "lingyun.abp", ElementName = "localization")] + public class XmlLocalizationFile + { + [XmlElement("culture")] + public CultureInfo Culture { get; set; } = new CultureInfo(); + + [XmlArray("texts")] + [XmlArrayItem("text")] + public LocalizationText[] Texts { get; set; } = new LocalizationText[0]; + + public XmlLocalizationFile() { } + + public XmlLocalizationFile(string culture) + { + Culture = new CultureInfo(culture); + } + + public XmlLocalizationFile(string culture, LocalizationText[] texts) + { + Culture = new CultureInfo(culture); + Texts = texts; + } + + public void WriteToPath(string filePath) + { + var fileName = Path.Combine(filePath, Culture.Name + ".xml"); + using FileStream fileStream = new(fileName, FileMode.Create, FileAccess.Write); + InternalWrite(fileStream, Encoding.UTF8); + } + + private void InternalWrite(Stream stream, Encoding encoding) + { + if (encoding == null) + { + throw new ArgumentNullException("encoding"); + } + + XmlSerializer serializer = new(GetType()); + XmlWriterSettings settings = new() + { + Indent = true, + NewLineChars = "\r\n", + Encoding = encoding, + IndentChars = " " + }; + + using XmlWriter writer = XmlWriter.Create(stream, settings); + serializer.Serialize(writer, this); + writer.Close(); + } + } + + [Serializable] + public class CultureInfo + { + [XmlAttribute("name")] + public string Name { get; set; } + + public CultureInfo() + { + } + + public CultureInfo(string name) + { + Name = name; + } + } + + [Serializable] + public class LocalizationText + { + [XmlAttribute("key")] + public string Key { get; set; } + + [XmlAttribute("value")] + public string Value { get; set; } + + public LocalizationText() + { + + } + + public LocalizationText(string key, string value) + { + Key = key; + Value = value; + } + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlPhysicalFileLocalizationResourceContributor.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlPhysicalFileLocalizationResourceContributor.cs new file mode 100644 index 000000000..fc3161bfa --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlPhysicalFileLocalizationResourceContributor.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.FileProviders; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Localization.Xml +{ + public class XmlPhysicalFileLocalizationResourceContributor : XmlFileLocalizationResourceContributorBase + { + private readonly string _filePath; + + public XmlPhysicalFileLocalizationResourceContributor(string filePath) + : base(filePath) + { + _filePath = filePath; + } + + protected override IFileProvider BuildFileProvider(LocalizationResourceInitializationContext context) + { + return new PhysicalFileProvider(_filePath); + } + + protected override Dictionary CreateDictionaries(IFileProvider fileProvider, string filePath) + { + var dictionaries = new Dictionary(); + + foreach (var file in fileProvider.GetDirectoryContents(string.Empty)) + { + if (file.IsDirectory || !CanParseFile(file)) + { + continue; + } + + var dictionary = CreateDictionaryFromFile(file); + if (dictionaries.ContainsKey(dictionary.CultureName)) + { + throw new AbpException($"{file.GetVirtualOrPhysicalPathOrNull()} dictionary has a culture name '{dictionary.CultureName}' which is already defined!"); + } + + dictionaries[dictionary.CultureName] = dictionary; + } + + return dictionaries; + } + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlVirtualFileLocalizationResourceContributor.cs b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlVirtualFileLocalizationResourceContributor.cs new file mode 100644 index 000000000..e6ced1dfe --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/LINGYUN/Abp/Localization/Xml/XmlVirtualFileLocalizationResourceContributor.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Localization.Xml +{ + public class XmlVirtualFileLocalizationResourceContributor : XmlFileLocalizationResourceContributorBase + { + public XmlVirtualFileLocalizationResourceContributor(string filePath) + : base(filePath) + { + } + + protected override IFileProvider BuildFileProvider(LocalizationResourceInitializationContext context) + { + return context.ServiceProvider.GetRequiredService(); + } + } +} diff --git a/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/README.md b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/README.md new file mode 100644 index 000000000..9c810b056 --- /dev/null +++ b/aspnet-core/modules/localization/LINGYUN.Abp.Localization.Xml/README.md @@ -0,0 +1,64 @@ +# LINGYUN.Abp.Localization.Xml + +## 模块说明 + +本地化组件的Xml文档集成,内置PhysicalFileProvider与VirtualFileProvider实现 + +### 基础模块 + +### 高阶模块 + +### 权限定义 + +### 功能定义 + +### 配置定义 + +### 如何使用 + + +```csharp + + [DependsOn( + typeof(AbpLocalizationXmlModule))] + public class YouProjectModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + // 当前项目中的虚拟文件系统目录,详情见: https://docs.abp.io/en/abp/latest/Virtual-File-System + .AddVirtualXml("/LINGYUN/Abp/Localization/Xml/Resources") + // 一般配置在宿主项目中, 写入宿主项目中存储xml文件的绝对路径(受PhysicalFileProvider的限制) + // 详情见: https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.fileproviders.physicalfileprovider?view=dotnet-plat-ext-5.0 + .AddPhysicalXml(Path.Combine(Directory.GetCurrentDirectory(), "Resources")); + }); + } + } + +``` +Xml文件格式如下 + +序列化: [XmlLocalizationFile](./LINGYUN/Abp/Localization/Xml/XmlLocalizationFile.cs) 类型实现 + +```xml + + + + + + + + + + +``` + +### 更新日志 diff --git a/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs b/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs index 30c2737f5..e3d1737f1 100644 --- a/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs +++ b/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs @@ -19,7 +19,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using StackExchange.Redis; using System; +using System.IO; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Encodings.Web; using System.Text.Unicode; @@ -41,6 +43,7 @@ using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.MySQL; using Volo.Abp.FeatureManagement.EntityFrameworkCore; using Volo.Abp.Identity; +using Volo.Abp.IdentityServer; using Volo.Abp.IdentityServer.Jwt; using Volo.Abp.Json; using Volo.Abp.Json.SystemTextJson; @@ -89,6 +92,7 @@ namespace AuthServer.Host public override void PreConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); + var hostingEnvironment = context.Services.GetHostingEnvironment(); PreConfigure(options => { @@ -100,6 +104,29 @@ namespace AuthServer.Host }) .UseDashboard(); }); + + var cerConfig = configuration.GetSection("Certificates"); + if (hostingEnvironment.IsProduction() && + cerConfig.Exists()) + { + // 开发环境下存在证书配置 + // 且证书文件存在则使用自定义的证书文件来启动Ids服务器 + var cerPath = Path.Combine(hostingEnvironment.ContentRootPath, cerConfig["CerPath"]); + if (File.Exists(cerPath)) + { + PreConfigure(options => + { + options.AddDeveloperSigningCredential = false; + }); + + var cer = new X509Certificate2(cerPath, cerConfig["Password"]); + + PreConfigure(builder => + { + builder.AddSigningCredential(cer); + }); + } + } } public override void ConfigureServices(ServiceConfigurationContext context) @@ -280,6 +307,7 @@ namespace AuthServer.Host } else { + // 需要实现一个错误页面 app.UseErrorPage(); app.UseHsts(); } diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN.Abp.Localization.Json.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN.Abp.Localization.Json.Tests.csproj new file mode 100644 index 000000000..6aa92b3f2 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN.Abp.Localization.Json.Tests.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonTestBase.cs new file mode 100644 index 000000000..d00ba4aed --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonTestBase.cs @@ -0,0 +1,8 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.Localization.Json +{ + public abstract class AbpLocalizationJsonTestBase : AbpTestsBase + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonTestModule.cs new file mode 100644 index 000000000..fcd3c15c4 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/AbpLocalizationJsonTestModule.cs @@ -0,0 +1,23 @@ +using LINGYUN.Abp.Tests; +using System.IO; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Localization.Json +{ + [DependsOn( + typeof(AbpTestsBaseModule), + typeof(AbpLocalizationJsonModule))] + public class AbpLocalizationJsonTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Add("en") + .AddPhysicalJson(Path.Combine(Directory.GetCurrentDirectory(), "TestResources")); + }); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/JsonLocalizationTest.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/JsonLocalizationTest.cs new file mode 100644 index 000000000..6dd1b8c84 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/JsonLocalizationTest.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Localization; +using Shouldly; +using Volo.Abp.Localization; +using Xunit; + +namespace LINGYUN.Abp.Localization.Json +{ + public class JsonLocalizationTest : AbpLocalizationJsonTestBase + { + private readonly IStringLocalizer _localizer; + + public JsonLocalizationTest() + { + _localizer = GetRequiredService>(); + } + + + [Fact] + public void Should_Get_Physical_Localized_Text_If_Defined_In_Current_Culture() + { + using (CultureHelper.Use("zh-Hans")) + { + _localizer["Hello China"].Value.ShouldBe("China 你好!"); + } + + using (CultureHelper.Use("en")) + { + _localizer["Hello China"].Value.ShouldBe("Hello China!"); + } + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/LocalizationTestResource.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/LocalizationTestResource.cs new file mode 100644 index 000000000..209adf0c7 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/LINGYUN/Abp/Localization/Json/LocalizationTestResource.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.Localization.Json +{ + public class LocalizationTestResource + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/TestResources/en.json b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/TestResources/en.json new file mode 100644 index 000000000..843f59c19 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/TestResources/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Hello China": "Hello China!" + } +} \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/TestResources/zh-Hans.json b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/TestResources/zh-Hans.json new file mode 100644 index 000000000..a60bd9936 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Json.Tests/TestResources/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Hello China": "China 你好!" + } +} \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN.Abp.Localization.Xml.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN.Abp.Localization.Xml.Tests.csproj new file mode 100644 index 000000000..acce4cd0c --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN.Abp.Localization.Xml.Tests.csproj @@ -0,0 +1,37 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlTestBase.cs new file mode 100644 index 000000000..b2c7cbd89 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlTestBase.cs @@ -0,0 +1,8 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.Localization.Xml +{ + public abstract class AbpLocalizationXmlTestBase : AbpTestsBase + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlTestModule.cs new file mode 100644 index 000000000..2c5023832 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/AbpLocalizationXmlTestModule.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.Tests; +using System.IO; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Localization.Xml +{ + [DependsOn( + typeof(AbpTestsBaseModule), + typeof(AbpLocalizationXmlModule))] + public class AbpLocalizationXmlTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualXml("/LINGYUN/Abp/Localization/Xml/Resources") + .AddPhysicalXml(Path.Combine(Directory.GetCurrentDirectory(), "TestResources")); + }); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/LocalizationTestResource.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/LocalizationTestResource.cs new file mode 100644 index 000000000..f66b21c59 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/LocalizationTestResource.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.Localization.Xml +{ + public class LocalizationTestResource + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/Resources/en.xml b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/Resources/en.xml new file mode 100644 index 000000000..54ca06f6f --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/Resources/en.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/Resources/zh-Hans.xml b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/Resources/zh-Hans.xml new file mode 100644 index 000000000..560893002 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/Resources/zh-Hans.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/XmlLocalizationTest.cs b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/XmlLocalizationTest.cs new file mode 100644 index 000000000..8d1500989 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/LINGYUN/Abp/Localization/Xml/XmlLocalizationTest.cs @@ -0,0 +1,94 @@ +using Microsoft.Extensions.Localization; +using Shouldly; +using System.IO; +using Volo.Abp.Localization; +using Xunit; + +namespace LINGYUN.Abp.Localization.Xml +{ + public class XmlLocalizationTest : AbpLocalizationXmlTestBase + { + private readonly static string[] _localizerFiles = new string[] + { + Path.Combine("./TestResources", "zh-Hans.xml"), + Path.Combine("./TestResources", "en.xml") + }; + + private readonly IStringLocalizer _localizer; + + public XmlLocalizationTest() + { + _localizer = GetRequiredService>(); + } + + [Fact] + public void Should_Get_Physical_Localized_Text_If_Defined_In_Current_Culture() + { + Init(); + + using (CultureHelper.Use("zh-Hans")) + { + _localizer["Hello World"].Value.ShouldBe("世界你好!"); + _localizer["C# Test"].Value.ShouldBe("C#测试"); + } + + using (CultureHelper.Use("en")) + { + _localizer["Hello World"].Value.ShouldBe("Hello World!"); + _localizer["C# Test"].Value.ShouldBe("C# Test!"); + } + } + + [Fact] + public void Should_Get_Virtual_Localized_Text_If_Defined_In_Current_Culture() + { + using (CultureHelper.Use("zh-Hans")) + { + _localizer["Hello China"].Value.ShouldBe("China 你好!"); + } + + using (CultureHelper.Use("en")) + { + _localizer["Hello China"].Value.ShouldBe("Hello China!"); + } + } + + private static void Init() + { + var zhHansfile = new XmlLocalizationFile("zh-Hans") + { + Texts = new LocalizationText[] + { + new LocalizationText("Hello World", "世界你好!"), + new LocalizationText("C# Test", "C#测试") + } + }; + + zhHansfile.WriteToPath("./TestResources"); + + var enFile = new XmlLocalizationFile("en") + { + Texts = new LocalizationText[] + { + new LocalizationText("Hello World", "Hello World!"), + new LocalizationText("C# Test", "C# Test!") + } + }; + + enFile.WriteToPath("./TestResources"); + } + + public override void Dispose() + { + base.Dispose(); + + foreach (var file in _localizerFiles) + { + if (File.Exists(file)) + { + File.Delete(file); + } + } + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/TestResources/zh-Hans.xml b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/TestResources/zh-Hans.xml new file mode 100644 index 000000000..61b962d50 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Localization.Xml.Tests/TestResources/zh-Hans.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file