32 changed files with 1112 additions and 1 deletions
@ -0,0 +1,15 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Localization" Version="4.2.1" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="5.0.*" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Json |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpLocalizationModule))] |
||||
|
public class AbpLocalizationJsonModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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<string, ILocalizationDictionary> _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<string, LocalizedString> dictionary) |
||||
|
{ |
||||
|
GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary); |
||||
|
} |
||||
|
|
||||
|
protected virtual Dictionary<string, ILocalizationDictionary> 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<string, ILocalizationDictionary> CreateDictionaries() |
||||
|
{ |
||||
|
var dictionaries = new Dictionary<string, ILocalizationDictionary>(); |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Json |
||||
|
{ |
||||
|
public static class LocalizationResourceExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 添加Json本地磁盘文件支持
|
||||
|
/// </summary>
|
||||
|
/// <param name="localizationResource"></param>
|
||||
|
/// <param name="jsonFilePath"></param>
|
||||
|
/// <returns></returns>
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<YouResource>("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!" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
### 更新日志 |
||||
@ -0,0 +1,15 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Localization" Version="4.2.1" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="5.0.*"/> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Xml |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpLocalizationModule))] |
||||
|
public class AbpLocalizationXmlModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 添加Xml虚拟文件系统支持
|
||||
|
/// </summary>
|
||||
|
/// <param name="localizationResource"></param>
|
||||
|
/// <param name="virtualPath"></param>
|
||||
|
/// <returns></returns>
|
||||
|
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; |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 添加Xml本地磁盘文件支持
|
||||
|
/// </summary>
|
||||
|
/// <param name="localizationResource"></param>
|
||||
|
/// <param name="xmlFilePath"></param>
|
||||
|
/// <returns></returns>
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<string, ILocalizationDictionary> _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<string, LocalizedString> dictionary) |
||||
|
{ |
||||
|
GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary); |
||||
|
} |
||||
|
|
||||
|
protected virtual Dictionary<string, ILocalizationDictionary> 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<string, ILocalizationDictionary> CreateDictionaries(IFileProvider fileProvider, string filePath) |
||||
|
{ |
||||
|
var dictionaries = new Dictionary<string, ILocalizationDictionary>(); |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
@ -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<string, LocalizedString>(); |
||||
|
var dublicateNames = new List<string>(); |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<string, ILocalizationDictionary> CreateDictionaries(IFileProvider fileProvider, string filePath) |
||||
|
{ |
||||
|
var dictionaries = new Dictionary<string, ILocalizationDictionary>(); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<IVirtualFileProvider>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<YouProjectModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<YouResource>("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 |
||||
|
|
||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<localization xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="lingyun.abp"> |
||||
|
<culture name="en" /> |
||||
|
<texts> |
||||
|
<text key="Hello China" value="Hello China!" /> |
||||
|
<text key="Welcome" value="Welcome!" /> |
||||
|
</texts> |
||||
|
</localization> |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
### 更新日志 |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net5.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<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"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
<PackageReference Include="coverlet.collector" Version="1.3.0"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\localization\LINGYUN.Abp.Localization.Json\LINGYUN.Abp.Localization.Json.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Json |
||||
|
{ |
||||
|
public abstract class AbpLocalizationJsonTestBase : AbpTestsBase<AbpLocalizationJsonTestModule> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<LocalizationTestResource>("en") |
||||
|
.AddPhysicalJson(Path.Combine(Directory.GetCurrentDirectory(), "TestResources")); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<LocalizationTestResource> _localizer; |
||||
|
|
||||
|
public JsonLocalizationTest() |
||||
|
{ |
||||
|
_localizer = GetRequiredService<IStringLocalizer<LocalizationTestResource>>(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[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!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.Localization.Json |
||||
|
{ |
||||
|
public class LocalizationTestResource |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Hello China": "Hello China!" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Hello China": "China 你好!" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net5.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\Localization\Xml\Resources\en.xml" /> |
||||
|
<None Remove="LINGYUN\Abp\Localization\Xml\Resources\zh-Hans.xml" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\Localization\Xml\Resources\en.xml" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\Localization\Xml\Resources\zh-Hans.xml" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<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"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
<PackageReference Include="coverlet.collector" Version="1.3.0"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\localization\LINGYUN.Abp.Localization.Xml\LINGYUN.Abp.Localization.Xml.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Localization.Xml |
||||
|
{ |
||||
|
public abstract class AbpLocalizationXmlTestBase : AbpTestsBase<AbpLocalizationXmlTestModule> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpLocalizationXmlTestModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<LocalizationTestResource>("en") |
||||
|
.AddVirtualXml("/LINGYUN/Abp/Localization/Xml/Resources") |
||||
|
.AddPhysicalXml(Path.Combine(Directory.GetCurrentDirectory(), "TestResources")); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.Localization.Xml |
||||
|
{ |
||||
|
public class LocalizationTestResource |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<localization xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="lingyun.abp"> |
||||
|
<culture name="en" /> |
||||
|
<texts> |
||||
|
<text key="Hello China" value="Hello China!" /> |
||||
|
</texts> |
||||
|
</localization> |
||||
@ -0,0 +1,7 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<localization xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="lingyun.abp"> |
||||
|
<culture name="zh-Hans" /> |
||||
|
<texts> |
||||
|
<text key="Hello China" value="China 你好!" /> |
||||
|
</texts> |
||||
|
</localization> |
||||
@ -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<LocalizationTestResource> _localizer; |
||||
|
|
||||
|
public XmlLocalizationTest() |
||||
|
{ |
||||
|
_localizer = GetRequiredService<IStringLocalizer<LocalizationTestResource>>(); |
||||
|
} |
||||
|
|
||||
|
[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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<localization xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="lingyun.abp"> |
||||
|
<culture name="zh-Hans" /> |
||||
|
<texts> |
||||
|
<text key="Hello World" value="世界你好!" /> |
||||
|
<text key="C# Test" value="C#测试" /> |
||||
|
</texts> |
||||
|
</localization> |
||||
Loading…
Reference in new issue