diff --git a/common/src/DbLocalizationProvider/LocalizationProvider.cs b/common/src/DbLocalizationProvider/LocalizationProvider.cs index ecfcb57a..ca5a86b5 100644 --- a/common/src/DbLocalizationProvider/LocalizationProvider.cs +++ b/common/src/DbLocalizationProvider/LocalizationProvider.cs @@ -9,12 +9,9 @@ using System.Text.RegularExpressions; using DbLocalizationProvider.Abstractions; using DbLocalizationProvider.Internal; -using DbLocalizationProvider.Json; using DbLocalizationProvider.Queries; using DbLocalizationProvider.Sync; using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using JsonConverter = DbLocalizationProvider.Json.JsonConverter; namespace DbLocalizationProvider; @@ -28,8 +25,7 @@ public class LocalizationProvider : ILocalizationProvider private readonly ResourceKeyBuilder _keyBuilder; internal readonly IQueryExecutor _queryExecutor; private readonly ScanState _scanState; - private readonly JsonConverter _converter; - private readonly JsonSerializer _serializer; + private readonly ReflectionConverter _reflectionConverter; /// <summary> /// Creates new localization provider with all the required settings and services injected. @@ -54,12 +50,8 @@ public LocalizationProvider( _fallbackCollection = context.Value._fallbackCollection; _queryExecutor = queryExecutor; _scanState = scanState; - - _converter = new JsonConverter(_queryExecutor, _scanState); - _serializer = new JsonSerializer - { - ContractResolver = new StaticPropertyContractResolver() - }; + + _reflectionConverter = new ReflectionConverter(_queryExecutor, _scanState); } /// <summary> @@ -228,20 +220,7 @@ public T Translate<T>() /// <returns>Translated class</returns> public T Translate<T>(CultureInfo language) { - var className = typeof(T).FullName; - - var json = _converter.GetJson(className, language.Name, _fallbackCollection); - - // get the actual class Json representation (we need to select token through FQN of the class) - // to supported nested classes - we need to fix a bit resource key name - var jsonToken = json.SelectToken(className.Replace('+', '.')); - - if (jsonToken == null) - { - return (T)Activator.CreateInstance(typeof(T), new object[] { }); - } - - return jsonToken.ToObject<T>(_serializer); + return _reflectionConverter.Convert<T>(language.Name, _fallbackCollection); } /// <summary> diff --git a/common/src/DbLocalizationProvider/ReflectionConverter.cs b/common/src/DbLocalizationProvider/ReflectionConverter.cs new file mode 100644 index 00000000..ab47a9f5 --- /dev/null +++ b/common/src/DbLocalizationProvider/ReflectionConverter.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using DbLocalizationProvider.Abstractions; +using DbLocalizationProvider.Queries; +using DbLocalizationProvider.Sync; + +namespace DbLocalizationProvider; + +public class ReflectionConverter +{ + private readonly IQueryExecutor _queryExecutor; + private readonly ScanState _scanState; + + public ReflectionConverter(IQueryExecutor queryExecutor, ScanState scanState) + { + _queryExecutor = queryExecutor; + _scanState = scanState; + } + + public T Convert<T>(string languageName, FallbackLanguagesCollection fallbackCollection) + { + // TODO: Can the dictionary be cached? + // TODO: Can we go around query execution and use repository directly? + var resources = _queryExecutor + .Execute(new GetAllResources.Query()) + .ToDictionary(x => x.ResourceKey, StringComparer.Ordinal); + + var newObject = Activator.CreateInstance<T>(); + + FillProperties(newObject!, languageName, resources, fallbackCollection); + + return newObject; + } + + private void FillProperties(object instance, string languageName, Dictionary<string, LocalizationResource> resources, FallbackLanguagesCollection fallbackCollection) + { + var type = instance!.GetType(); + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static); + foreach (var propertyInfo in properties) + { + if (propertyInfo.MemberType == MemberTypes.NestedType) + { + var nestedObject = Activator.CreateInstance(propertyInfo.PropertyType); + if (nestedObject == null) + { + continue; + } + + FillProperties(nestedObject, languageName, resources, fallbackCollection); + + propertyInfo.SetValue(instance, nestedObject); + } + + if (propertyInfo.PropertyType != typeof(string)) + { + continue; + } + + string? translation; + string key = $"{type.FullName}.{propertyInfo.Name}"; + if (_scanState.UseResourceAttributeCache.TryGetValue(key, out var targetResourceKey)) + { + if (resources.TryGetValue(targetResourceKey, out var foundResource)) + { + translation = foundResource.Translations.GetValueWithFallback( + languageName, + fallbackCollection.GetFallbackLanguages(languageName)); + + propertyInfo.SetValue(instance, translation); + continue; + } + } + + if (!resources.TryGetValue(key, out var resource)) + { + continue; + } + + translation = resource.Translations.GetValueWithFallback( + languageName, + fallbackCollection.GetFallbackLanguages(languageName)); + + propertyInfo.SetValue(instance, translation); + } + } +}