Backend/e-suite.API/eSuite.API/Translation/TranslatorFactory.cs

217 lines
5.7 KiB
C#

using eSuite.API.SingleSignOn;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using System.Text.Json;
using System.Text.RegularExpressions;
using e_suite.API.Common.extensions;
namespace eSuite.API.Translation;
[AttributeUsage(AttributeTargets.Field)]
public class NamespaceValueAttribute : Attribute
{
public string Value { get; }
public NamespaceValueAttribute(string value)
{
Value = value;
}
}
public enum Namespaces
{
[NamespaceValue("common")]
Common,
[NamespaceValue("mailTypes")]
MailTypes,
[NamespaceValue("htmlIsland")]
HtmlIsland
}
public static class NamespaceExtensions
{
public static string ToNamespaceString(this Namespaces ns)
{
var type = ns.GetType();
var member = type.GetMember(ns.ToString()).FirstOrDefault();
var attr = member?
.GetCustomAttributes(typeof(NamespaceValueAttribute), false)
.Cast<NamespaceValueAttribute>()
.FirstOrDefault();
return attr?.Value ?? ns.ToString();
}
}
public static class I18nInterpolation
{
private static readonly Regex TokenRegex = new Regex(@"{{\s*(\w+)\s*}}",
RegexOptions.Compiled);
public static string Interpolate(string template, IDictionary<string, object> values)
{
if (template == null)
return string.Empty;
return TokenRegex.Replace(template, match =>
{
var key = match.Groups[1].Value;
if (values != null && values.TryGetValue(key, out var val))
return val?.ToString() ?? string.Empty;
return match.Value;
});
}
}
public interface ITranslator
{
string T(string key, object values = null);
}
public class Translator : ITranslator
{
private readonly IDictionary<string, string> _translations;
public Translator(IDictionary<string, string> translations)
{
_translations = translations;
}
public string T(string key, object values = null)
{
if (!_translations.TryGetValue(key, out var template))
return key;
var dict = values?
.GetType()
.GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(values));
return I18nInterpolation.Interpolate(template, dict);
}
}
public static class LocaleFallback
{
public static IEnumerable<string> ResolveFallbacks(string locale, string defaultLocale = "en")
{
if (!string.IsNullOrWhiteSpace(locale))
{
var parts = locale.Split('-');
// Default first
if (!string.Equals(locale, defaultLocale, StringComparison.OrdinalIgnoreCase))
yield return defaultLocale;
// Language-only
if (parts.Length > 1)
yield return parts[0];
// Full locale last (most specific)
yield return locale;
}
else
{
yield return defaultLocale;
}
}
}
public interface IJsonLocalizationService
{
Task<IDictionary<string, string>> LoadNamespaceAsync(string locale, string ns);
}
public class JsonLocalizationService : IJsonLocalizationService
{
private readonly IHttpClientFacade _http;
private readonly IMemoryCache _cache;
private readonly string _baseUrl;
public JsonLocalizationService(
IHttpClientFacade http,
IMemoryCache cache,
IConfiguration configuration
)
{
_http = http;
_cache = cache;
_baseUrl = configuration.GetConfigValue("BASE_URL", "baseUrl", "http://localhost:3000");
}
public Task<IDictionary<string, string>> LoadNamespaceAsync(string locale, string ns)
{
var cacheKey = $"i18n:{locale}:{ns}";
return _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return await LoadNamespaceInternalAsync(locale, ns);
});
}
private async Task<IDictionary<string, string>> LoadNamespaceInternalAsync(string locale, string ns)
{
var result = new Dictionary<string, string>();
foreach (var loc in LocaleFallback.ResolveFallbacks(locale))
{
var url = new Uri($"{_baseUrl}/locales/{loc}/{ns}.json");
try
{
var json = await _http.GetStringAsync(url, CancellationToken.None);
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
foreach (var kvp in dict)
result[kvp.Key] = kvp.Value;
}
catch
{
// ignore missing files
}
}
return result;
}
}
public interface ITranslatorFactory
{
string Locale { get; set; }
Task<ITranslator> Use(params Namespaces[] namespaces);
}
public class TranslatorFactory : ITranslatorFactory
{
private readonly IJsonLocalizationService _localizer;
public string Locale { get; set; } = "en-GB";
public TranslatorFactory(IJsonLocalizationService localizer)
{
_localizer = localizer;
}
public async Task<ITranslator> Use(params Namespaces[] namespaces)
{
var merged = new Dictionary<string, string>();
foreach (var ns in namespaces)
{
var nsString = ns.ToNamespaceString();
var dict = await _localizer.LoadNamespaceAsync(Locale, nsString);
foreach (var kvp in dict)
merged[kvp.Key] = kvp.Value;
}
return new Translator(merged);
}
}