diff --git a/Libraries/SPTarkov.Server.Core/Callbacks/DataCallbacks.cs b/Libraries/SPTarkov.Server.Core/Callbacks/DataCallbacks.cs index 9b60baf8..8f740905 100644 --- a/Libraries/SPTarkov.Server.Core/Callbacks/DataCallbacks.cs +++ b/Libraries/SPTarkov.Server.Core/Callbacks/DataCallbacks.cs @@ -11,7 +11,8 @@ public class DataCallbacks( HttpResponseUtil _httpResponseUtil, DatabaseService _databaseService, TraderController _traderController, - HideoutController _hideoutController + HideoutController _hideoutController, + LocaleService _localeService ) { /// @@ -133,10 +134,9 @@ public class DataCallbacks( public string GetLocalesGlobal(string url, EmptyRequestData _, string sessionID) { var localeId = url.Replace("/client/locale/", ""); - var locales = _databaseService.GetLocales(); - var result = locales.Global?[localeId].Value ?? locales.Global?.FirstOrDefault(m => m.Key == "en").Value.Value; + var locales = _localeService.GetLocaleDb(localeId); - return _httpResponseUtil.GetUnclearedBody(result); + return _httpResponseUtil.GetUnclearedBody(locales); } /// diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs index 2810c938..0a0e5715 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs @@ -296,15 +296,13 @@ public class GiveSptCommand( } /// - /// Return the desired locale, falls back to english if it cannot be found + /// Return the desired locale, falls back to english if it cannot be found /// /// Locale code, e.g. "fr" for french /// protected Dictionary GetGlobalsLocale(string desiredLocale) { - return _databaseService.GetLocales().Global.TryGetValue(desiredLocale, out var locale) - ? locale.Value - : _databaseService.GetLocales().Global["en"].Value; + return _localeService.GetLocaleDb(desiredLocale); } /** diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Server/LocaleBase.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Server/LocaleBase.cs index dc13319f..08e22db7 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Server/LocaleBase.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Server/LocaleBase.cs @@ -6,6 +6,8 @@ namespace SPTarkov.Server.Core.Models.Spt.Server; public record LocaleBase { [JsonPropertyName("global")] + /// DO NOT USE THIS PROPERTY DIRECTLY, USE LOCALESERVICE INSTEAD + /// THIS IS LAZY LOADED AND YOUR CHANGES WILL NOT BE SAVED public Dictionary>>? Global { get; diff --git a/Libraries/SPTarkov.Server.Core/Services/CustomLocaleService.cs b/Libraries/SPTarkov.Server.Core/Services/CustomLocaleService.cs deleted file mode 100644 index e6142c7f..00000000 --- a/Libraries/SPTarkov.Server.Core/Services/CustomLocaleService.cs +++ /dev/null @@ -1,75 +0,0 @@ -using SPTarkov.Server.Core.Models.Utils; -using SPTarkov.Common.Annotations; - -namespace SPTarkov.Server.Core.Services; - -[Injectable(InjectionType.Singleton)] -public class CustomLocaleService( - ISptLogger logger -) -{ - protected Dictionary> customServerLocales = new(); - protected Dictionary> customClientLocales = new(); - - - /// - /// Path should link to a folder containing every locale that should be added to the server locales - /// e.g. en.json for english, fr.json for french.
- /// Inside each JSON should be a Dictionary of the locale key and localised text - ///
- /// en/fr/de - /// locale key to store values against - /// Localised string to store - public void AddServerLocales(string locale, string localeKey, string localeValue) - { - AddToDictionary(locale, localeKey, localeValue, customServerLocales); - } - - /// - /// Path should link to a folder containing every locale that should be added to the game locales - /// e.g. en.json for english, fr.json for french.
- /// Inside each JSON should be a Dictionary of the locale key and localised text - ///
- /// en/fr/de - /// locale key to store values against - /// Localised string to store - public void AddGameLocales(string locale, string localeKey, string localeValue) - { - AddToDictionary(locale, localeKey, localeValue, customClientLocales); - } - - protected void AddToDictionary(string locale, string localeKey, string localeValue, - Dictionary> dictionaryToAddTo) - { - dictionaryToAddTo.TryAdd(locale, new Dictionary()); - if (!dictionaryToAddTo.TryGetValue(locale, out var localeDictToAddTo)) - { - logger.Error($"Unable to get custom locale dictionary keyed by: {locale}"); - - return; - } - - if (!localeDictToAddTo.TryAdd(localeKey, localeValue)) - { - logger.Error($"Unable to add: {localeKey} {localeValue} to custom locale dictionary: {locale}"); - } - } - - public string? GetServerValue(string locale, string localeKey) - { - return GetValueFromDictionary(locale, localeKey, customServerLocales); - } - - public string? GetClientValue(string locale, string localeKey) - { - return GetValueFromDictionary(locale, localeKey, customClientLocales); - } - - protected string? GetValueFromDictionary(string locale, string localeKey, - Dictionary> dictionaryToGetFrom) - { - return dictionaryToGetFrom.TryGetValue(locale, out var localeDictToGetFrom) - ? localeDictToGetFrom.GetValueOrDefault(localeKey) // Locale exists, look up value or return null - : null; // No locale (e.g. en/fr/de) at all - } -} diff --git a/Libraries/SPTarkov.Server.Core/Services/I18nService.cs b/Libraries/SPTarkov.Server.Core/Services/I18nService.cs index fdfa4cc0..f49ad083 100644 --- a/Libraries/SPTarkov.Server.Core/Services/I18nService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/I18nService.cs @@ -11,7 +11,7 @@ public class I18nService private readonly Dictionary _fallbacks; private readonly FileUtil _fileUtil; private readonly JsonUtil _jsonUtil; - private readonly CustomLocaleService _customLocaleService; + private readonly LocaleService _localeService; private readonly Dictionary>> _loadedLocales = new(); private HashSet _locales; @@ -20,11 +20,11 @@ public class I18nService public I18nService( FileUtil fileUtil, JsonUtil jsonUtil, - CustomLocaleService customLocaleService, HashSet locales, Dictionary fallbacks, string defaultLocale, - string directory + string directory, + LocaleService localeService ) { _locales = locales; @@ -32,8 +32,8 @@ public class I18nService _defaultLocale = defaultLocale; _directory = directory; _jsonUtil = jsonUtil; - _customLocaleService = customLocaleService; _fileUtil = fileUtil; + _localeService = localeService; Initialize(); } @@ -91,22 +91,28 @@ public class I18nService public string GetLocalisedValue(string key) { + // get loaded locales for set key if (!_loadedLocales.TryGetValue(_setLocale, out var locales)) { + // if we are unable to get the "loadedLocales" for the set locale, return the key return key; } + // searching through loaded locales for given key if (!locales.Value.TryGetValue(key, out var value)) { + // if the key is not found in loaded locales + // check if the key is found in the default locale _loadedLocales.TryGetValue(_defaultLocale, out var defaults); if (!defaults.Value.TryGetValue(key, out value)) { - value = _customLocaleService.GetClientValue(_defaultLocale, key); + value = _localeService.GetLocaleDb(_defaultLocale).FirstOrDefault(x => x.Key == key).Value; } return value ?? key; } + // if the key is found in the server locale, return the value return value; } diff --git a/Libraries/SPTarkov.Server.Core/Services/LocaleService.cs b/Libraries/SPTarkov.Server.Core/Services/LocaleService.cs index 7fbdb73d..878b64d4 100644 --- a/Libraries/SPTarkov.Server.Core/Services/LocaleService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/LocaleService.cs @@ -3,6 +3,7 @@ using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Utils.Json; namespace SPTarkov.Server.Core.Services; @@ -13,24 +14,68 @@ public class LocaleService( ConfigServer _configServer ) { + // we have to LazyLoad the data from the database and then combine it with the custom data before returning it protected LocaleConfig _localeConfig = _configServer.GetConfig(); + protected Dictionary> customClientLocales = new Dictionary>(); /// - /// Get the eft globals db file based on the configured locale in config/locale.json, if not found, fall back to 'en' + /// Get the eft globals db file based on the configured locale in config/locale.json, if not found, fall back to 'en' + /// This will contain Custom locales added by mods /// - /// Dictionary - public Dictionary GetLocaleDb() + /// Dictionary of locales for desired language - en/fr/cn + public Dictionary GetLocaleDb(string? language = null) { - if (_databaseServer.GetTables().Locales.Global.TryGetValue(GetDesiredGameLocale(), out var desiredLocale)) + var languageToUse = string.IsNullOrEmpty(language) ? GetDesiredGameLocale() : language; + Dictionary? localeToReturn; + + // if it can't get locales for language provided, default to en + if (TryGetLocaleDbWithCustomLocales(languageToUse, out localeToReturn) || + TryGetLocaleDbWithCustomLocales("en", out localeToReturn)) { - return desiredLocale.Value; + // TODO: need to see if this needs to be cloned + return RemovePraporTestMessage(localeToReturn); } - _logger.Warning( - $"Unable to find desired locale file using locale: {GetDesiredGameLocale()} from config/locale.json, falling back to 'en'" - ); + throw new Exception($"unable to get locales from either {languageToUse} or en"); + } - return _databaseServer.GetTables().Locales.Global["en"].Value; + /// + /// Attempts to retrieve the locale database for the specified language key, including custom locales if available. + /// + /// The language key for which the locale database should be retrieved. + /// The resulting locale database as a dictionary, or null if the operation fails. + /// True if the locale database was successfully retrieved, otherwise false. + private bool TryGetLocaleDbWithCustomLocales(string languageKey, out Dictionary? localeToReturn) + { + localeToReturn = null; + if (!_databaseServer.GetTables().Locales.Global.TryGetValue(languageKey, out var keyedLocales)) + { + return false; + } + + localeToReturn = keyedLocales.Value; + + if (customClientLocales.TryGetValue(languageKey, out var customClientLocale)) + { + // there were custom locales for this language + localeToReturn = CombineDbWithCustomLocales(localeToReturn, customClientLocale); + } + + return true; + } + + + /// + /// Combines the provided database locales with custom locales, ensuring that all entries are merged into a single dictionary. + /// Custom locale entries will overwrite existing keys from the database locales if conflicts occur. + /// + /// + /// The dictionary containing locale entries from the database. + /// The dictionary containing custom locale entries to be merged. + /// A dictionary representing the merged result of database and custom locales. + private Dictionary CombineDbWithCustomLocales(Dictionary dbLocales, Dictionary customLocales) + { + return dbLocales.Union(customLocales).ToDictionary(x => x.Key, x => x.Value); } /// @@ -179,4 +224,35 @@ public class LocaleService( { return GetLocaleDb().Keys.Where(x => x.StartsWith(partialKey)).ToList(); } + + public void AddCustomClientLocale(string locale, string localeKey, string localeValue) + { + AddToDictionary(locale, localeKey, localeValue, customClientLocales); + } + + private void AddToDictionary(string locale, string localeKey, string localeValue, + Dictionary> dictionaryToAddTo) + { + dictionaryToAddTo.TryAdd(locale, new Dictionary()); + if (!dictionaryToAddTo.TryGetValue(locale, out var localeDictToAddTo)) + { + _logger.Error($"Unable to get custom locale dictionary keyed by: {locale}"); + + return; + } + + if (!localeDictToAddTo.TryAdd(localeKey, localeValue)) + { + _logger.Error($"Unable to add: {localeKey} {localeValue} to custom locale dictionary: {locale}"); + } + } + + /// + /// Blank out the "test" mail message from prapor + /// + protected Dictionary RemovePraporTestMessage(Dictionary dbLocales) + { + dbLocales["61687e2c3e526901fa76baf9"] = ""; + return dbLocales; + } } diff --git a/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs b/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs index e4a5eb21..681114f6 100644 --- a/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs @@ -22,7 +22,6 @@ public class LocalisationService public LocalisationService( ISptLogger logger, RandomUtil randomUtil, - CustomLocaleService customLocaleService, DatabaseServer databaseServer, LocaleService localeService, JsonUtil jsonUtil, @@ -36,11 +35,11 @@ public class LocalisationService _i18nService = new I18nService( fileUtil, jsonUtil, - customLocaleService, localeService.GetServerSupportedLocales().ToHashSet(), localeService.GetLocaleFallbacks(), "en", - "./Assets/database/locales/server" + "./Assets/database/locales/server", + localeService ); _i18nService.SetLocaleByKey(localeService.GetDesiredServerLocale()); } diff --git a/Libraries/SPTarkov.Server.Core/Services/Mod/CustomItemService.cs b/Libraries/SPTarkov.Server.Core/Services/Mod/CustomItemService.cs index 0339a16c..a9943d17 100644 --- a/Libraries/SPTarkov.Server.Core/Services/Mod/CustomItemService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/Mod/CustomItemService.cs @@ -17,7 +17,8 @@ public class CustomItemService( DatabaseService databaseService, ItemHelper itemHelper, ItemBaseClassService itemBaseClassService, - ICloner cloner + ICloner cloner, + LocaleService localeService ) { /// @@ -206,17 +207,9 @@ public class CustomItemService( newLocaleDetails ??= localeDetails[localeDetails.Keys.FirstOrDefault()]; - // Create new record in locale file - if (!databaseService.GetLocales().Global.TryGetValue(shortNameKey.Key, out var desiredGlobal)) - { - logger.Error($"Unable to add locale keys to {shortNameKey.Key}"); - - return; - } - - desiredGlobal.Value[$"{newItemId} Name"] = newLocaleDetails.Name; - desiredGlobal.Value[$"{newItemId} ShortName"] = newLocaleDetails.ShortName; - desiredGlobal.Value[$"{newItemId} Description"] = newLocaleDetails.Description; + localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} Name", newLocaleDetails.Name); + localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} ShortName", newLocaleDetails.ShortName); + localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} Description", newLocaleDetails.Description); } } diff --git a/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs b/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs index 6180764a..cb5c93f6 100644 --- a/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs @@ -101,8 +101,6 @@ public class PostDbLoadService( RemoveNewBeginningRequirementFromPrestige(); - RemovePraporTestMessage(); - ValidateQuestAssortUnlocksExist(); if (_seasonalEventService.IsAutomaticEventDetectionEnabled()) @@ -145,7 +143,7 @@ public class PostDbLoadService( } /// - /// Merge custom achievements into achievement db table + /// Merge custom achievements into achievement db table /// protected void MergeCustomAchievements() { @@ -519,19 +517,6 @@ public class PostDbLoadService( } } - /// - /// Blank out the "test" mail message from prapor - /// - protected void RemovePraporTestMessage() - { - // Iterate over all languages (e.g. "en", "fr") - var locales = _databaseService.GetLocales(); - foreach (var localeKvP in locales.Global) - { - locales.Global[localeKvP.Key].Value["61687e2c3e526901fa76baf9"] = ""; - } - } - /// /// Check for any missing assorts inside each traders assort.json data, checking against traders questassort.json /// diff --git a/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs b/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs index 93868b81..006514d9 100644 --- a/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs @@ -21,7 +21,8 @@ public class SeasonalEventService( BotHelper _botHelper, ProfileHelper _profileHelper, //DatabaseImporter _databaseImporter, - ConfigServer _configServer + ConfigServer _configServer, + LocaleService _localeService ) { private bool _christmasEventActive; @@ -1060,9 +1061,8 @@ public class SeasonalEventService( protected void RenameBitcoin() { - var enLocale = _databaseService.GetLocales().Global["en"]; - enLocale.Value[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} Name"] = "Physical SPT Coin"; - enLocale.Value[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} ShortName"] = "0.2SPT"; + _localeService.AddCustomClientLocale("en", $"{ItemTpl.BARTER_PHYSICAL_BITCOIN} Name", "Physical SPT Coin"); + _localeService.AddCustomClientLocale("en", $"{ItemTpl.BARTER_PHYSICAL_BITCOIN} ShortName", "0.2SPT"); } /// diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index a83a4839..24ad31c9 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -6,9 +6,7 @@ using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils; using Serilog; -using Serilog.Events; using Serilog.Exceptions; -using Serilog.Extensions.Logging; using SPTarkov.Common.Semver; using SPTarkov.Common.Semver.Implementations; using SPTarkov.Server.Logger; @@ -32,7 +30,7 @@ public static class Program // validate and sort mods, this will also discard any mods that are invalid var sortedLoadedMods = ValidateMods(mods); - // for harmony we use the original list, as some mods may only be bepinex patches only + // for harmony, we use the original list, as some mods may only be bepinex patches only HarmonyBootstrapper.LoadAllPatches(mods.SelectMany(asm => asm.Assemblies).ToList()); // register SPT components @@ -49,7 +47,7 @@ public static class Program try { var watermark = serviceProvider.GetService(); - // Initialize Watermak + // Initialize Watermark watermark?.Initialize(); var appContext = serviceProvider.GetService(); @@ -72,7 +70,7 @@ public static class Program var app = serviceProvider.GetService(); app?.Run().Wait(); - // Run garbage collection now server is ready to start + // Run garbage collection now the server is ready to start GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); @@ -89,7 +87,6 @@ public static class Program { Console.WriteLine(ex); logger.LogCritical(ex, "Critical exception, stopping server..."); - // throw ex; } }