217 lines
5.7 KiB
C#
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);
|
|
}
|
|
} |