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() .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 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 _translations; public Translator(IDictionary 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 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> 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> 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> LoadNamespaceInternalAsync(string locale, string ns) { var result = new Dictionary(); 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>(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 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 Use(params Namespaces[] namespaces) { var merged = new Dictionary(); 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); } }