diff --git a/API/Controllers/LocaleController.cs b/API/Controllers/LocaleController.cs new file mode 100644 index 0000000000..c210daac7e --- /dev/null +++ b/API/Controllers/LocaleController.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using API.Data; +using API.Services; +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers; + +public class LocaleController : BaseApiController +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILocalizationService _localizationService; + private static readonly IReadOnlyList AllLocales = new List() { "en" }; + + public LocaleController(IUnitOfWork unitOfWork, ILocalizationService localizationService) + { + _unitOfWork = unitOfWork; + _localizationService = localizationService; + } + + [HttpGet] + public ActionResult> GetAllLocales() + { + return Ok(AllLocales); + } +} diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs index 5faec1cde0..5d5f5136f5 100644 --- a/API/Data/DataContext.cs +++ b/API/Data/DataContext.cs @@ -100,6 +100,9 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .Property(b => b.BookReaderWritingStyle) .HasDefaultValue(WritingStyle.Horizontal); + builder.Entity() + .Property(b => b.Locale) + .HasDefaultValue("en"); builder.Entity() .Property(b => b.AllowScrobbling) diff --git a/API/Entities/AppUserPreferences.cs b/API/Entities/AppUserPreferences.cs index d3a980dc7c..640ecc1ead 100644 --- a/API/Entities/AppUserPreferences.cs +++ b/API/Entities/AppUserPreferences.cs @@ -127,6 +127,10 @@ public class AppUserPreferences /// UI Site Global Setting: Should series reviews be shared with all users in the server /// public bool ShareReviews { get; set; } = false; + /// + /// UI Site Global Setting: The language locale that should be used for the user + /// + public string Locale { get; set; } public AppUser AppUser { get; set; } = null!; public int AppUserId { get; set; } diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index a020bc35dd..f6c2844d9b 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -66,6 +66,8 @@ public static void AddApplicationServices(this IServiceCollection services, ICon services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); diff --git a/API/Services/LocalizationService.cs b/API/Services/LocalizationService.cs index a9ac7e69da..d9fb651bfe 100644 --- a/API/Services/LocalizationService.cs +++ b/API/Services/LocalizationService.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; namespace API.Services; @@ -8,19 +11,21 @@ namespace API.Services; public interface ILocalizationService { - void LoadLanguage(string languageCode); - //string Get(string key); + Task> LoadLanguage(string languageCode); + Task Get(string locale, string key, params object[] args); } public class LocalizationService : ILocalizationService { private readonly IDirectoryService _directoryService; + private readonly IMemoryCache _cache; private readonly string _localizationDirectory; - private dynamic? _languageLocale; - public LocalizationService(IDirectoryService directoryService, IHostEnvironment environment) + + public LocalizationService(IDirectoryService directoryService, IHostEnvironment environment, IMemoryCache cache) { _directoryService = directoryService; + _cache = cache; if (environment.IsDevelopment()) { _localizationDirectory = directoryService.FileSystem.Path.Join( @@ -40,20 +45,45 @@ public LocalizationService(IDirectoryService directoryService, IHostEnvironment /// /// /// - public void LoadLanguage(string languageCode) + public async Task> LoadLanguage(string languageCode) { var languageFile = _directoryService.FileSystem.Path.Join(_localizationDirectory, languageCode + ".json"); if (!_directoryService.FileSystem.FileInfo.New(languageFile).Exists) throw new ArgumentException($"Language {languageCode} does not exist"); - var json = _directoryService.FileSystem.File.ReadAllText(languageFile); - _languageLocale = JsonSerializer.Deserialize(json); + var json = await _directoryService.FileSystem.File.ReadAllTextAsync(languageFile); + return Newtonsoft.Json.JsonConvert.DeserializeObject>(json); } - // public string Get(string key) - // { - // if (_languageLocale == null) return key; - // return _languageLocale. - // - // } + public async Task Get(string locale, string key, params object[] args) + { + // Check if the translation for the given locale is cached + if (!_cache.TryGetValue($"{locale}_{key}", out string translatedString)) + { + // Load the locale JSON file + var translationData = await LoadLanguage(locale); + + // Find the translation for the given key + if (translationData.TryGetValue(key, out string value)) + { + translatedString = value; + + // Cache the translation for subsequent requests + _cache.Set($"{locale}_{key}", translatedString, TimeSpan.FromMinutes(15)); // Cache for 15 minutes + } + else + { + // If the key is not found, use the key as the translated string + translatedString = key; + } + } + + // Format the translated string with arguments + if (args.Length > 0) + { + translatedString = string.Format(translatedString, args); + } + + return translatedString; + } }