Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize translation to object conversion #344

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 4 additions & 25 deletions common/src/DbLocalizationProvider/LocalizationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.
Expand All @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
88 changes: 88 additions & 0 deletions common/src/DbLocalizationProvider/ReflectionConverter.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading