mirror of https://github.com/abpframework/abp.git
32 changed files with 990 additions and 6 deletions
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.TestBase |
||||
|
{ |
||||
|
public class AbpTestBaseModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Volo.Abp.Internal |
||||
|
{ |
||||
|
internal static class Utf8Helper |
||||
|
{ |
||||
|
public static string ReadStringFromStream(Stream stream) |
||||
|
{ |
||||
|
var bytes = stream.GetAllBytes(); |
||||
|
var skipCount = HasBom(bytes) ? 3 : 0; |
||||
|
return Encoding.UTF8.GetString(bytes, skipCount, bytes.Length - skipCount); |
||||
|
} |
||||
|
|
||||
|
private static bool HasBom(byte[] bytes) |
||||
|
{ |
||||
|
if (bytes.Length < 3) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,173 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Globalization; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
//TODO: Remove old/unused methods!
|
||||
|
|
||||
|
public class AbpDictionaryBasedStringLocalizer : IStringLocalizer |
||||
|
{ |
||||
|
public LocalizationResource Resource { get; } |
||||
|
|
||||
|
public AbpDictionaryBasedStringLocalizer(LocalizationResource resource) |
||||
|
{ |
||||
|
Resource = resource; |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) |
||||
|
{ |
||||
|
return GetAllStrings(CultureInfo.CurrentUICulture.Name, includeParentCultures); |
||||
|
} |
||||
|
|
||||
|
public IStringLocalizer WithCulture(CultureInfo culture) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
LocalizedString IStringLocalizer.this[string name] |
||||
|
{ |
||||
|
get { return GetString(name); } |
||||
|
} |
||||
|
|
||||
|
LocalizedString IStringLocalizer.this[string name, params object[] arguments] |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var localizedString = GetString(name); |
||||
|
return new LocalizedString(name, string.Format(localizedString.Value, arguments, localizedString.ResourceNotFound, localizedString.SearchedLocation)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public LocalizedString GetString(string name) |
||||
|
{ |
||||
|
return GetString(name, CultureInfo.CurrentUICulture.Name); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public LocalizedString GetString(string name, string cultureName) |
||||
|
{ |
||||
|
var value = GetStringOrNull(name, cultureName); |
||||
|
|
||||
|
if (value == null) |
||||
|
{ |
||||
|
return new LocalizedString(name, name, true); |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
public LocalizedString GetStringOrNull(string name, bool tryDefaults = true) |
||||
|
{ |
||||
|
return GetStringOrNull(name, CultureInfo.CurrentUICulture.Name, tryDefaults); |
||||
|
} |
||||
|
|
||||
|
public LocalizedString GetStringOrNull(string name, string cultureName, bool tryDefaults = true) |
||||
|
{ |
||||
|
var dictionaries = Resource.DictionaryProvider.Dictionaries; |
||||
|
|
||||
|
//Try to get from original dictionary (with country code)
|
||||
|
ILocalizationDictionary originalDictionary; |
||||
|
if (dictionaries.TryGetValue(cultureName, out originalDictionary)) |
||||
|
{ |
||||
|
var strOriginal = originalDictionary.GetOrNull(name); |
||||
|
if (strOriginal != null) |
||||
|
{ |
||||
|
return new LocalizedString(name, strOriginal.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!tryDefaults) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
//Try to get from same language dictionary (without country code)
|
||||
|
if (cultureName.Contains("-")) //Example: "tr-TR" (length=5)
|
||||
|
{ |
||||
|
ILocalizationDictionary langDictionary; |
||||
|
if (dictionaries.TryGetValue(GetBaseCultureName(cultureName), out langDictionary)) |
||||
|
{ |
||||
|
var strLang = langDictionary.GetOrNull(name); |
||||
|
if (strLang != null) |
||||
|
{ |
||||
|
return new LocalizedString(name, strLang.Value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Try to get from default language
|
||||
|
var defaultDictionary = Resource.DictionaryProvider.Dictionaries[Resource.DefaultCultureName]; //TODO: What if not contains a default dictionary?
|
||||
|
if (defaultDictionary == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var strDefault = defaultDictionary.GetOrNull(name); |
||||
|
if (strDefault == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return new LocalizedString(name, strDefault.Value); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IReadOnlyList<LocalizedString> GetAllStrings(string cultureName, bool includeDefaults = true) |
||||
|
{ |
||||
|
//TODO: Can be optimized (example: if it's already default dictionary, skip overriding)
|
||||
|
|
||||
|
var dictionaries = Resource.DictionaryProvider.Dictionaries; |
||||
|
|
||||
|
//Create a temp dictionary to build
|
||||
|
var allStrings = new Dictionary<string, LocalizedString>(); |
||||
|
|
||||
|
if (includeDefaults) |
||||
|
{ |
||||
|
//Fill all strings from default dictionary
|
||||
|
var defaultDictionary = Resource.DictionaryProvider.Dictionaries[Resource.DefaultCultureName]; //TODO: What if not contains a default dictionary?
|
||||
|
if (defaultDictionary != null) |
||||
|
{ |
||||
|
foreach (var defaultDictString in defaultDictionary.GetAllStrings()) |
||||
|
{ |
||||
|
allStrings[defaultDictString.Name] = new LocalizedString(defaultDictString.Name, defaultDictString.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Overwrite all strings from the language based on country culture
|
||||
|
if (cultureName.Contains("-")) |
||||
|
{ |
||||
|
ILocalizationDictionary langDictionary; |
||||
|
if (dictionaries.TryGetValue(GetBaseCultureName(cultureName), out langDictionary)) |
||||
|
{ |
||||
|
foreach (var langString in langDictionary.GetAllStrings()) |
||||
|
{ |
||||
|
allStrings[langString.Name] = new LocalizedString(langString.Name, langString.Value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Overwrite all strings from the original dictionary
|
||||
|
ILocalizationDictionary originalDictionary; |
||||
|
if (dictionaries.TryGetValue(cultureName, out originalDictionary)) |
||||
|
{ |
||||
|
foreach (var originalLangString in originalDictionary.GetAllStrings()) |
||||
|
{ |
||||
|
allStrings[originalLangString.Name] = new LocalizedString(originalLangString.Name, originalLangString.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return allStrings.Values.ToImmutableList(); |
||||
|
} |
||||
|
|
||||
|
private static string GetBaseCultureName(string cultureName) |
||||
|
{ |
||||
|
return cultureName.Contains("-") |
||||
|
? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) |
||||
|
: cultureName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public class AbpLocalizationOptions |
||||
|
{ |
||||
|
public LocalizationResourceList Resources { get; } |
||||
|
public AbpStringLocalizerList Resolvers { get; } |
||||
|
|
||||
|
public AbpLocalizationOptions() |
||||
|
{ |
||||
|
Resources = new LocalizationResourceList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Linq; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Localization.Json; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public class AbpStringLocalizerFactory : IStringLocalizerFactory |
||||
|
{ |
||||
|
private readonly ResourceManagerStringLocalizerFactory _innerFactory; |
||||
|
private readonly AbpLocalizationOptions _abpLocalizationOptions; |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
private readonly ConcurrentDictionary<Type, AbpDictionaryBasedStringLocalizer> _localizerCache; |
||||
|
|
||||
|
//TODO: It's better to use decorator pattern for IStringLocalizerFactory instead of getting ResourceManagerStringLocalizerFactory as a dependency.
|
||||
|
public AbpStringLocalizerFactory( |
||||
|
ResourceManagerStringLocalizerFactory innerFactory, |
||||
|
IOptions<AbpLocalizationOptions> abpLocalizationOptions, IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_innerFactory = innerFactory; |
||||
|
_serviceProvider = serviceProvider; |
||||
|
_abpLocalizationOptions = abpLocalizationOptions.Value; |
||||
|
|
||||
|
_localizerCache = new ConcurrentDictionary<Type, AbpDictionaryBasedStringLocalizer>();; |
||||
|
} |
||||
|
|
||||
|
public virtual IStringLocalizer Create(Type resourceSource) |
||||
|
{ |
||||
|
//TODO: Optimize!
|
||||
|
|
||||
|
var localizationResource = _abpLocalizationOptions.Resources.FirstOrDefault(l => l.ResourceType == resourceSource); |
||||
|
if (localizationResource == null) |
||||
|
{ |
||||
|
return _innerFactory.Create(resourceSource); |
||||
|
} |
||||
|
|
||||
|
return _localizerCache.GetOrAdd(resourceSource, _ => CreateAbpStringLocalizer(localizationResource)); |
||||
|
} |
||||
|
|
||||
|
private AbpDictionaryBasedStringLocalizer CreateAbpStringLocalizer(LocalizationResource resource) |
||||
|
{ |
||||
|
resource.Initialize(_serviceProvider); |
||||
|
|
||||
|
//Use JSON/XML/...etc based provider that reads resource from source and creates a dictionary
|
||||
|
//Extend dictionary with extensions
|
||||
|
//Wrap reader by wrappers (like db wrapper which implement multitenancy/regions and so on...)
|
||||
|
|
||||
|
|
||||
|
//Notes: Localizer will be cached, so wrappers are responsible to cache/invalidate themselves!
|
||||
|
|
||||
|
var localizer = new AbpDictionaryBasedStringLocalizer(resource); //TODO: !!!
|
||||
|
|
||||
|
//TODO: Wrap with DB provider or other premium sources
|
||||
|
|
||||
|
return localizer; |
||||
|
} |
||||
|
|
||||
|
public virtual IStringLocalizer Create(string baseName, string location) |
||||
|
{ |
||||
|
//TODO: Investigate when this is called?
|
||||
|
|
||||
|
return _innerFactory.Create(baseName, location); |
||||
|
} |
||||
|
|
||||
|
internal static void Replace(IServiceCollection services) |
||||
|
{ |
||||
|
services.Replace(ServiceDescriptor.Singleton<IStringLocalizerFactory, AbpStringLocalizerFactory>()); |
||||
|
services.AddSingleton<ResourceManagerStringLocalizerFactory>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public class AbpStringLocalizerList : List<IAbpStringLocalizer> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public interface IAbpStringLocalizer |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a dictionary that is used to find a localized string.
|
||||
|
/// </summary>
|
||||
|
public interface ILocalizationDictionary |
||||
|
{ |
||||
|
string CultureName { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets/sets a string for this dictionary with given name (key).
|
||||
|
/// </summary>
|
||||
|
/// <param name="name">Name to get/set</param>
|
||||
|
LocalString this[string name] { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a <see cref="LocalString"/> for given <paramref name="name"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="name">Name (key) to get localized string</param>
|
||||
|
/// <returns>The localized string or null if not found in this dictionary</returns>
|
||||
|
LocalString GetOrNull(string name); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a list of all strings in this dictionary.
|
||||
|
/// </summary>
|
||||
|
/// <returns>List of all <see cref="LocalString"/> object</returns>
|
||||
|
IReadOnlyList<LocalString> GetAllStrings(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public interface ILocalizationDictionaryProvider |
||||
|
{ |
||||
|
IDictionary<string, ILocalizationDictionary> Dictionaries { get; } |
||||
|
|
||||
|
void Initialize(LocalizationResource resource); |
||||
|
|
||||
|
void Extend(ILocalizationDictionary dictionary); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Reflection; |
||||
|
using Volo.Abp.Internal; |
||||
|
|
||||
|
namespace Volo.Abp.Localization.Json |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides localization dictionaries from JSON files embedded into an <see cref="Assembly"/>.
|
||||
|
/// </summary>
|
||||
|
public class JsonEmbeddedFileLocalizationDictionaryProvider : LocalizationDictionaryProviderBase |
||||
|
{ |
||||
|
private readonly Assembly _assembly; |
||||
|
private readonly string _rootNamespace; |
||||
|
|
||||
|
public JsonEmbeddedFileLocalizationDictionaryProvider(Assembly assembly, string rootNamespace) |
||||
|
{ |
||||
|
_assembly = assembly; |
||||
|
_rootNamespace = rootNamespace; |
||||
|
} |
||||
|
|
||||
|
public override void Initialize(LocalizationResource resource) |
||||
|
{ |
||||
|
var resourceNames = _assembly.GetManifestResourceNames(); |
||||
|
foreach (var resourceName in resourceNames) |
||||
|
{ |
||||
|
if (resourceName.StartsWith(_rootNamespace)) |
||||
|
{ |
||||
|
using (var stream = _assembly.GetManifestResourceStream(resourceName)) |
||||
|
{ |
||||
|
var jsonString = Utf8Helper.ReadStringFromStream(stream); |
||||
|
|
||||
|
var dictionary = CreateJsonLocalizationDictionary(jsonString); |
||||
|
if (Dictionaries.ContainsKey(dictionary.CultureName)) |
||||
|
{ |
||||
|
throw new AbpException(resource.ResourceType.FullName + " source contains more than one dictionary for the culture: " + dictionary.CultureName); |
||||
|
} |
||||
|
|
||||
|
Dictionaries[dictionary.CultureName] = dictionary; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual JsonLocalizationDictionary CreateJsonLocalizationDictionary(string jsonString) |
||||
|
{ |
||||
|
return JsonLocalizationDictionary.BuildFromJsonString(jsonString); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
//using System.IO;
|
||||
|
//using Abp.Localization.Dictionaries.Xml;
|
||||
|
//using Abp.Localization.Sources;
|
||||
|
|
||||
|
//namespace Abp.Localization.Dictionaries.Json
|
||||
|
//{
|
||||
|
// /// <summary>
|
||||
|
// /// Provides localization dictionaries from json files in a directory.
|
||||
|
// /// </summary>
|
||||
|
// public class JsonFileLocalizationDictionaryProvider : LocalizationDictionaryProviderBase
|
||||
|
// {
|
||||
|
// private readonly string _directoryPath;
|
||||
|
|
||||
|
// /// <summary>
|
||||
|
// /// Creates a new <see cref="JsonFileLocalizationDictionaryProvider" />.
|
||||
|
// /// </summary>
|
||||
|
// /// <param name="directoryPath">Path of the dictionary that contains all related XML files</param>
|
||||
|
// public JsonFileLocalizationDictionaryProvider(string directoryPath)
|
||||
|
// {
|
||||
|
// _directoryPath = directoryPath;
|
||||
|
// }
|
||||
|
|
||||
|
// public override void Initialize(string sourceName)
|
||||
|
// {
|
||||
|
// var fileNames = Directory.GetFiles(_directoryPath, "*.json", SearchOption.TopDirectoryOnly);
|
||||
|
|
||||
|
// foreach (var fileName in fileNames)
|
||||
|
// {
|
||||
|
// var dictionary = CreateJsonLocalizationDictionary(fileName);
|
||||
|
// if (Dictionaries.ContainsKey(dictionary.CultureInfo.Name))
|
||||
|
// {
|
||||
|
// throw new AbpInitializationException(sourceName + " source contains more than one dictionary for the culture: " + dictionary.CultureInfo.Name);
|
||||
|
// }
|
||||
|
|
||||
|
// Dictionaries[dictionary.CultureInfo.Name] = dictionary;
|
||||
|
|
||||
|
// if (fileName.EndsWith(sourceName + ".json"))
|
||||
|
// {
|
||||
|
// if (DefaultDictionary != null)
|
||||
|
// {
|
||||
|
// throw new AbpInitializationException("Only one default localization dictionary can be for source: " + sourceName);
|
||||
|
// }
|
||||
|
|
||||
|
// DefaultDictionary = dictionary;
|
||||
|
// }
|
||||
|
// }
|
||||
|
// }
|
||||
|
|
||||
|
// protected virtual JsonLocalizationDictionary CreateJsonLocalizationDictionary(string fileName)
|
||||
|
// {
|
||||
|
// return JsonLocalizationDictionary.BuildFromFile(fileName);
|
||||
|
// }
|
||||
|
// }
|
||||
|
//}
|
||||
@ -0,0 +1,97 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.IO; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Serialization; |
||||
|
|
||||
|
namespace Volo.Abp.Localization.Json |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class is used to build a localization dictionary from json.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Use static Build methods to create instance of this class.
|
||||
|
/// </remarks>
|
||||
|
public class JsonLocalizationDictionary : LocalizationDictionary |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Private constructor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="cultureName">Culture of the dictionary</param>
|
||||
|
private JsonLocalizationDictionary(string cultureName) |
||||
|
: base(cultureName) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Builds an <see cref="JsonLocalizationDictionary" /> from given file.
|
||||
|
/// </summary>
|
||||
|
/// <param name="filePath">Path of the file</param>
|
||||
|
public static JsonLocalizationDictionary BuildFromFile(string filePath) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return BuildFromJsonString(File.ReadAllText(filePath)); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
throw new AbpException("Invalid localization file format! " + filePath, ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Builds an <see cref="JsonLocalizationDictionary" /> from given json string.
|
||||
|
/// </summary>
|
||||
|
/// <param name="jsonString">Json string</param>
|
||||
|
public static JsonLocalizationDictionary BuildFromJsonString(string jsonString) |
||||
|
{ |
||||
|
JsonLocalizationFile jsonFile; |
||||
|
try |
||||
|
{ |
||||
|
jsonFile = JsonConvert.DeserializeObject<JsonLocalizationFile>( |
||||
|
jsonString, |
||||
|
new JsonSerializerSettings |
||||
|
{ |
||||
|
ContractResolver = new CamelCasePropertyNamesContractResolver() |
||||
|
}); |
||||
|
} |
||||
|
catch (JsonException ex) |
||||
|
{ |
||||
|
throw new AbpException("Can not parse json string. " + ex.Message); |
||||
|
} |
||||
|
|
||||
|
var cultureCode = jsonFile.Culture; |
||||
|
if (string.IsNullOrEmpty(cultureCode)) |
||||
|
{ |
||||
|
throw new AbpException("Culture is empty in language json file."); |
||||
|
} |
||||
|
|
||||
|
var dictionary = new JsonLocalizationDictionary(cultureCode); |
||||
|
var dublicateNames = new List<string>(); |
||||
|
foreach (var item in jsonFile.Texts) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(item.Key)) |
||||
|
{ |
||||
|
throw new AbpException("The key is empty in given json string."); |
||||
|
} |
||||
|
|
||||
|
if (dictionary.Contains(item.Key)) |
||||
|
{ |
||||
|
dublicateNames.Add(item.Key); |
||||
|
} |
||||
|
|
||||
|
dictionary[item.Key] = new LocalString(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 dictionary; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Localization.Json |
||||
|
{ |
||||
|
public class JsonLocalizationFile |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Culture name; eg : en , en-us, zh-CN
|
||||
|
/// </summary>
|
||||
|
public string Culture { get; set; } |
||||
|
|
||||
|
public Dictionary<string, string> Texts { get; } |
||||
|
|
||||
|
public JsonLocalizationFile() |
||||
|
{ |
||||
|
Texts = new Dictionary<string, string>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public class LocalString |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Value { get; set; } |
||||
|
|
||||
|
public LocalString(string name, string value) |
||||
|
{ |
||||
|
Name = name; |
||||
|
Value = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a simple implementation of <see cref="ILocalizationDictionary"/> interface.
|
||||
|
/// </summary>
|
||||
|
public class LocalizationDictionary : ILocalizationDictionary, IEnumerable<LocalString> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public string CultureName { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual LocalString this[string name] |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var localizedString = GetOrNull(name); |
||||
|
return localizedString == null ? null : localizedString; |
||||
|
} |
||||
|
set { _dictionary[name] = value; } |
||||
|
} |
||||
|
|
||||
|
private readonly Dictionary<string, LocalString> _dictionary; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new <see cref="LocalizationDictionary"/> object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="cultureName">Culture of the dictionary</param>
|
||||
|
public LocalizationDictionary(string cultureName) |
||||
|
{ |
||||
|
CultureName = cultureName; |
||||
|
_dictionary = new Dictionary<string, LocalString>(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual LocalString GetOrNull(string name) |
||||
|
{ |
||||
|
LocalString localizedString; |
||||
|
return _dictionary.TryGetValue(name, out localizedString) ? localizedString : null; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual IReadOnlyList<LocalString> GetAllStrings() |
||||
|
{ |
||||
|
return _dictionary.Values.ToImmutableList(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual IEnumerator<LocalString> GetEnumerator() |
||||
|
{ |
||||
|
return GetAllStrings().GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||
|
{ |
||||
|
return GetAllStrings().GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
protected bool Contains(string name) |
||||
|
{ |
||||
|
return _dictionary.ContainsKey(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public abstract class LocalizationDictionaryProviderBase : ILocalizationDictionaryProvider |
||||
|
{ |
||||
|
public LocalizationResource ResourceType { get; private set; } |
||||
|
|
||||
|
public ILocalizationDictionary DefaultDictionary { get; protected set; } |
||||
|
|
||||
|
public IDictionary<string, ILocalizationDictionary> Dictionaries { get; private set; } |
||||
|
|
||||
|
protected LocalizationDictionaryProviderBase() |
||||
|
{ |
||||
|
Dictionaries = new Dictionary<string, ILocalizationDictionary>(); |
||||
|
} |
||||
|
|
||||
|
public virtual void Initialize(LocalizationResource resourceType) |
||||
|
{ |
||||
|
ResourceType = resourceType; |
||||
|
} |
||||
|
|
||||
|
public void Extend(ILocalizationDictionary dictionary) |
||||
|
{ |
||||
|
//Add
|
||||
|
ILocalizationDictionary existingDictionary; |
||||
|
if (!Dictionaries.TryGetValue(dictionary.CultureName, out existingDictionary)) |
||||
|
{ |
||||
|
Dictionaries[dictionary.CultureName] = dictionary; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
//Override
|
||||
|
var localizedStrings = dictionary.GetAllStrings(); |
||||
|
foreach (var localizedString in localizedStrings) |
||||
|
{ |
||||
|
existingDictionary[localizedString.Name] = localizedString; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public class LocalizationResource |
||||
|
{ |
||||
|
public Type ResourceType { get; } |
||||
|
|
||||
|
public string DefaultCultureName { get; set; } |
||||
|
|
||||
|
public ILocalizationDictionaryProvider DictionaryProvider |
||||
|
{ |
||||
|
get => _dictionaryProvider; |
||||
|
set |
||||
|
{ |
||||
|
Check.NotNull(value, nameof(value)); |
||||
|
_dictionaryProvider = value; |
||||
|
} |
||||
|
} |
||||
|
private ILocalizationDictionaryProvider _dictionaryProvider; |
||||
|
|
||||
|
public LocalizationResource([NotNull] Type resourceType, [NotNull] string defaultCultureName, [NotNull] ILocalizationDictionaryProvider dictionaryProvider) |
||||
|
{ |
||||
|
Check.NotNull(resourceType, nameof(resourceType)); |
||||
|
Check.NotNull(defaultCultureName, nameof(defaultCultureName)); |
||||
|
Check.NotNull(dictionaryProvider, nameof(dictionaryProvider)); |
||||
|
|
||||
|
ResourceType = resourceType; |
||||
|
DefaultCultureName = defaultCultureName; |
||||
|
DictionaryProvider = dictionaryProvider; |
||||
|
} |
||||
|
|
||||
|
public virtual void Initialize(IServiceProvider serviceProvider) //TODO: Create a LocalizationResourceInitializationContext!
|
||||
|
{ |
||||
|
DictionaryProvider.Initialize(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public class LocalizationResourceList : List<LocalizationResource> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Localization.Json; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public static class LocalizationResourceListExtensions |
||||
|
{ |
||||
|
public static void AddJson<TResource>(this LocalizationResourceList resourceList, [NotNull] string defaultCultureName) |
||||
|
{ |
||||
|
var type = typeof(TResource); |
||||
|
|
||||
|
resourceList.Add( |
||||
|
new LocalizationResource( |
||||
|
type, |
||||
|
defaultCultureName, |
||||
|
new JsonEmbeddedFileLocalizationDictionaryProvider( |
||||
|
type.Assembly, |
||||
|
type.Namespace |
||||
|
) |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.DependencyInjection |
||||
|
{ |
||||
|
public class DependencyInjection_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Singletons_Should_Resolve_Transients_Independent_From_Current_Scope() |
||||
|
{ |
||||
|
//Arrange
|
||||
|
|
||||
|
var services = new ServiceCollection(); |
||||
|
|
||||
|
services |
||||
|
.AddSingleton<MySingletonService>() |
||||
|
.AddTransient<MyTransientServiceUsesSingleton>() |
||||
|
.AddTransient<MyTransientService>(); |
||||
|
|
||||
|
MySingletonService singletonService; |
||||
|
|
||||
|
using (var serviceProvider = services.BuildServiceProvider()) |
||||
|
{ |
||||
|
//Act
|
||||
|
|
||||
|
using (var scope = serviceProvider.CreateScope()) |
||||
|
{ |
||||
|
scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
||||
|
scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
||||
|
} |
||||
|
|
||||
|
using (var scope = serviceProvider.CreateScope()) |
||||
|
{ |
||||
|
scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
||||
|
scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
||||
|
scope.ServiceProvider.GetRequiredService<MySingletonService>().ShouldNotBeDisposed(); |
||||
|
} |
||||
|
|
||||
|
singletonService = serviceProvider.GetRequiredService<MySingletonService>(); |
||||
|
singletonService.ShouldNotBeDisposed(); |
||||
|
} |
||||
|
|
||||
|
singletonService.ShouldBeDisposed(); |
||||
|
} |
||||
|
|
||||
|
private class MyTransientServiceUsesSingleton |
||||
|
{ |
||||
|
private readonly MySingletonService _singletonService; |
||||
|
|
||||
|
public MyTransientServiceUsesSingleton(MySingletonService singletonService) |
||||
|
{ |
||||
|
_singletonService = singletonService; |
||||
|
} |
||||
|
|
||||
|
public void DoIt() |
||||
|
{ |
||||
|
_singletonService.DoIt(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class MySingletonService |
||||
|
{ |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
private readonly List<MyTransientService> _instances; |
||||
|
|
||||
|
public MySingletonService(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
_instances = new List<MyTransientService>(); |
||||
|
} |
||||
|
|
||||
|
public void DoIt() |
||||
|
{ |
||||
|
_instances.Add(_serviceProvider.GetRequiredService<MyTransientService>()); |
||||
|
} |
||||
|
|
||||
|
public void ShouldNotBeDisposed() |
||||
|
{ |
||||
|
foreach (var instance in _instances) |
||||
|
{ |
||||
|
instance.IsDisposed.ShouldBeFalse(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void ShouldBeDisposed() |
||||
|
{ |
||||
|
foreach (var instance in _instances) |
||||
|
{ |
||||
|
instance.IsDisposed.ShouldBeTrue(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class MyTransientService : IDisposable |
||||
|
{ |
||||
|
public bool IsDisposed { get; private set; } |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
IsDisposed = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Localization.Source; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.TestBase; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Localization |
||||
|
{ |
||||
|
public class AbpLocalization_Tests : AbpIntegratedTest<AbpLocalization_Tests.TestModule> |
||||
|
{ |
||||
|
private readonly IStringLocalizer<LocalizationTestResource> _localizer; |
||||
|
|
||||
|
public AbpLocalization_Tests() |
||||
|
{ |
||||
|
_localizer = GetRequiredService<IStringLocalizer<LocalizationTestResource>>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Get_Same_Text_If_Not_Defined_Anywhere() |
||||
|
{ |
||||
|
const string text = "A string that is not defined anywhere!"; |
||||
|
|
||||
|
_localizer[text].Value.ShouldBe(text); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Get_Localized_Text_If_Defined_In_Current_Culture() |
||||
|
{ |
||||
|
using (AbpCultureHelper.Use("en")) |
||||
|
{ |
||||
|
_localizer["Car"].Value.ShouldBe("Car"); |
||||
|
_localizer["CarPlural"].Value.ShouldBe("Cars"); |
||||
|
} |
||||
|
|
||||
|
using (AbpCultureHelper.Use("tr")) |
||||
|
{ |
||||
|
_localizer["Car"].Value.ShouldBe("Araba"); |
||||
|
_localizer["CarPlural"].Value.ShouldBe("Araba"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[DependsOn(typeof(AbpTestBaseModule))] |
||||
|
public class TestModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(IServiceCollection services) |
||||
|
{ |
||||
|
services.Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources.AddJson<LocalizationTestResource>("en"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Localization.Source |
||||
|
{ |
||||
|
public sealed class LocalizationTestResource |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Hello <b>{0}</b>.": "Hello <b>{0}</b>", |
||||
|
"Car": "Car", |
||||
|
"CarPlural": "Cars" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"culture": "tr", |
||||
|
"texts": { |
||||
|
"Hello <b>{0}</b>.": "Merhaba <b>{0}</b>", |
||||
|
"Car": "Araba", |
||||
|
"CarPlural": "Araba" |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue