Browse Source

Initial json localization implementation.

pull/137/head
Halil İbrahim Kalkan 9 years ago
parent
commit
1af65651e1
  1. 5
      src/Volo.Abp.TestBase/AbpIntegratedTest.cs
  2. 9
      src/Volo.Abp.TestBase/AbpTestBaseModule.cs
  3. 10
      src/Volo.Abp.TestBase/AbpTestBaseWithServiceProvider.cs
  4. 1
      src/Volo.Abp/Volo.Abp.csproj
  5. 4
      src/Volo.Abp/Volo/Abp/AbpKernelModule.cs
  6. 30
      src/Volo.Abp/Volo/Abp/Internal/Utf8Helper.cs
  7. 2
      src/Volo.Abp/Volo/Abp/Localization/AbpCultureHelper.cs
  8. 173
      src/Volo.Abp/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
  9. 13
      src/Volo.Abp/Volo/Abp/Localization/AbpLocalizationOptions.cs
  10. 76
      src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerFactory.cs
  11. 8
      src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerList.cs
  12. 6
      src/Volo.Abp/Volo/Abp/Localization/IAbpStringLocalizer.cs
  13. 31
      src/Volo.Abp/Volo/Abp/Localization/ILocalizationDictionary.cs
  14. 13
      src/Volo.Abp/Volo/Abp/Localization/ILocalizationDictionaryProvider.cs
  15. 48
      src/Volo.Abp/Volo/Abp/Localization/Json/JsonEmbeddedFileLocalizationDictionaryProvider.cs
  16. 54
      src/Volo.Abp/Volo/Abp/Localization/Json/JsonFileLocalizationDictionaryProvider.cs
  17. 97
      src/Volo.Abp/Volo/Abp/Localization/Json/JsonLocalizationDictionary.cs
  18. 19
      src/Volo.Abp/Volo/Abp/Localization/Json/JsonLocalizationFile.cs
  19. 15
      src/Volo.Abp/Volo/Abp/Localization/LocalString.cs
  20. 68
      src/Volo.Abp/Volo/Abp/Localization/LocalizationDictionary.cs
  21. 41
      src/Volo.Abp/Volo/Abp/Localization/LocalizationDictionaryProviderBase.cs
  22. 39
      src/Volo.Abp/Volo/Abp/Localization/LocalizationResource.cs
  23. 9
      src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceList.cs
  24. 24
      src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceListExtensions.cs
  25. 2
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/MvcLocalization_Tests.cs
  26. 2
      test/Volo.Abp.Tests/System/StringExtensions_Tests.cs
  27. 10
      test/Volo.Abp.Tests/Volo.Abp.Tests.csproj
  28. 108
      test/Volo.Abp.Tests/Volo/Abp/DependencyInjection/DependencyInjection_Tests.cs
  29. 56
      test/Volo.Abp.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
  30. 7
      test/Volo.Abp.Tests/Volo/Abp/Localization/Source/LocalizationTestResource.cs
  31. 8
      test/Volo.Abp.Tests/Volo/Abp/Localization/Source/en.json
  32. 8
      test/Volo.Abp.Tests/Volo/Abp/Localization/Source/tr.json

5
src/Volo.Abp.TestBase/AbpIntegratedTest.cs

@ -4,7 +4,7 @@ using Volo.Abp.Modularity;
namespace Volo.Abp.TestBase
{
public class AbpIntegratedTest<TStartupModule> : AbpTestBaseWithServiceProvider, IDisposable
public abstract class AbpIntegratedTest<TStartupModule> : AbpTestBaseWithServiceProvider, IDisposable
where TStartupModule : IAbpModule
{
protected IAbpApplication Application { get; }
@ -54,8 +54,7 @@ namespace Volo.Abp.TestBase
{
return services.BuildServiceProviderFromFactory();
}
public void Dispose()
{
Application.Shutdown();

9
src/Volo.Abp.TestBase/AbpTestBaseModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.TestBase
{
public class AbpTestBaseModule : AbpModule
{
}
}

10
src/Volo.Abp.TestBase/AbpTestBaseWithServiceProvider.cs

@ -88,5 +88,15 @@ namespace Volo.Abp.TestBase
}
}
}
protected virtual T GetService<T>()
{
return ServiceProvider.GetService<T>();
}
protected virtual T GetRequiredService<T>()
{
return ServiceProvider.GetRequiredService<T>();
}
}
}

1
src/Volo.Abp/Volo.Abp.csproj

@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />

4
src/Volo.Abp/Volo/Abp/AbpKernelModule.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.ApiVersioning;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Reflection;
@ -32,6 +33,9 @@ namespace Volo.Abp
{
services.AddOptions();
services.AddLogging();
services.AddLocalization();
AbpStringLocalizerFactory.Replace(services);
services.AddAssemblyOf<AbpKernelModule>();

30
src/Volo.Abp/Volo/Abp/Internal/Utf8Helper.cs

@ -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;
}
}
}

2
src/Volo.Abp/Volo/Abp/Globalization/AbpCultureHelper.cs → src/Volo.Abp/Volo/Abp/Localization/AbpCultureHelper.cs

@ -2,7 +2,7 @@
using System.Globalization;
using JetBrains.Annotations;
namespace Volo.Abp.Globalization
namespace Volo.Abp.Localization
{
public static class AbpCultureHelper
{

173
src/Volo.Abp/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs

@ -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;
}
}
}

13
src/Volo.Abp/Volo/Abp/Localization/AbpLocalizationOptions.cs

@ -0,0 +1,13 @@
namespace Volo.Abp.Localization
{
public class AbpLocalizationOptions
{
public LocalizationResourceList Resources { get; }
public AbpStringLocalizerList Resolvers { get; }
public AbpLocalizationOptions()
{
Resources = new LocalizationResourceList();
}
}
}

76
src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerFactory.cs

@ -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>();
}
}
}

8
src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerList.cs

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Volo.Abp.Localization
{
public class AbpStringLocalizerList : List<IAbpStringLocalizer>
{
}
}

6
src/Volo.Abp/Volo/Abp/Localization/IAbpStringLocalizer.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.Localization
{
public interface IAbpStringLocalizer
{
}
}

31
src/Volo.Abp/Volo/Abp/Localization/ILocalizationDictionary.cs

@ -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();
}
}

13
src/Volo.Abp/Volo/Abp/Localization/ILocalizationDictionaryProvider.cs

@ -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);
}
}

48
src/Volo.Abp/Volo/Abp/Localization/Json/JsonEmbeddedFileLocalizationDictionaryProvider.cs

@ -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);
}
}
}

54
src/Volo.Abp/Volo/Abp/Localization/Json/JsonFileLocalizationDictionaryProvider.cs

@ -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);
// }
// }
//}

97
src/Volo.Abp/Volo/Abp/Localization/Json/JsonLocalizationDictionary.cs

@ -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;
}
}
}

19
src/Volo.Abp/Volo/Abp/Localization/Json/JsonLocalizationFile.cs

@ -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>();
}
}
}

15
src/Volo.Abp/Volo/Abp/Localization/LocalString.cs

@ -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;
}
}
}

68
src/Volo.Abp/Volo/Abp/Localization/LocalizationDictionary.cs

@ -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);
}
}
}

41
src/Volo.Abp/Volo/Abp/Localization/LocalizationDictionaryProviderBase.cs

@ -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;
}
}
}
}

39
src/Volo.Abp/Volo/Abp/Localization/LocalizationResource.cs

@ -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);
}
}
}

9
src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceList.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Volo.Abp.Localization
{
public class LocalizationResourceList : List<LocalizationResource>
{
}
}

24
src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceListExtensions.cs

@ -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
)
)
);
}
}
}

2
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/MvcLocalization_Tests.cs

@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization
{
public class MvcLocalization_Tests : AspNetCoreMvcTestBase
{
private readonly IStringLocalizer _localizer;
private readonly IStringLocalizer<MvcLocalization_Tests> _localizer;
public MvcLocalization_Tests()
{

2
test/Volo.Abp.Tests/System/StringExtensions_Tests.cs

@ -1,5 +1,5 @@
using Shouldly;
using Volo.Abp.Globalization;
using Volo.Abp.Localization;
using Xunit;
namespace System

10
test/Volo.Abp.Tests/Volo.Abp.Tests.csproj

@ -15,6 +15,16 @@
<EmbeddedResource Include="Volo\Abp\EmbeddedFiles\MyResources\js\*.js" Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
</ItemGroup>
<ItemGroup>
<None Remove="Volo\Abp\Localization\Source\en.json" />
<None Remove="Volo\Abp\Localization\Source\tr.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\Localization\Source\en.json" />
<EmbeddedResource Include="Volo\Abp\Localization\Source\tr.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />

108
test/Volo.Abp.Tests/Volo/Abp/DependencyInjection/DependencyInjection_Tests.cs

@ -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;
}
}
}
}

56
test/Volo.Abp.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs

@ -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");
});
}
}
}
}

7
test/Volo.Abp.Tests/Volo/Abp/Localization/Source/LocalizationTestResource.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Localization.Source
{
public sealed class LocalizationTestResource
{
}
}

8
test/Volo.Abp.Tests/Volo/Abp/Localization/Source/en.json

@ -0,0 +1,8 @@
{
"culture": "en",
"texts": {
"Hello <b>{0}</b>.": "Hello <b>{0}</b>",
"Car": "Car",
"CarPlural": "Cars"
}
}

8
test/Volo.Abp.Tests/Volo/Abp/Localization/Source/tr.json

@ -0,0 +1,8 @@
{
"culture": "tr",
"texts": {
"Hello <b>{0}</b>.": "Merhaba <b>{0}</b>",
"Car": "Araba",
"CarPlural": "Araba"
}
}
Loading…
Cancel
Save