diff --git a/common/src/DbLocalizationProvider/LocalizationProvider.cs b/common/src/DbLocalizationProvider/LocalizationProvider.cs index ecfcb57a..26a0994a 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; /// /// 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, _keyBuilder); } /// @@ -221,27 +213,14 @@ public T Translate() } /// - /// Give a type to this method and it will return instance of the type but translated + /// Give a type to this method, and it will return instance of the type but translated /// /// Type of the target class you want to translate /// Language to use during translation /// Translated class public T Translate(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(_serializer); + return _reflectionConverter.Convert(language.Name, _fallbackCollection); } /// diff --git a/common/src/DbLocalizationProvider/ReflectionConverter.cs b/common/src/DbLocalizationProvider/ReflectionConverter.cs new file mode 100644 index 00000000..15865612 --- /dev/null +++ b/common/src/DbLocalizationProvider/ReflectionConverter.cs @@ -0,0 +1,94 @@ +// Copyright (c) Stefan Holm Olsen. All rights reserved. +// Licensed under Apache-2.0. See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using DbLocalizationProvider.Abstractions; +using DbLocalizationProvider.Queries; +using DbLocalizationProvider.Sync; + +namespace DbLocalizationProvider; + +/// +/// Much faster translations to object converter based on reflection +/// +public class ReflectionConverter(IQueryExecutor queryExecutor, ScanState scanState, ResourceKeyBuilder keyBuilder) +{ + /// + /// Creates an object of and fills with translations + /// + /// Specify in which language you are going to use + /// Fallback languages collection + /// Specify target object type + /// If all is good, will return object of type filled with translations of matching keys + public T Convert(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(); + + FillProperties(newObject!, languageName, resources, fallbackCollection); + + return newObject; + } + + private void FillProperties( + object instance, + string languageName, + Dictionary 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; + var key = keyBuilder.BuildResourceKey(type, propertyInfo.Name); + if (scanState.UseResourceAttributeCache.TryGetValue(key, out var targetResourceKey) + && 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); + } + } +}