From f83b4a9138fd34f0fb9d466fae5a27481f77cb2a Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 17 Apr 2025 13:19:55 +0100 Subject: [PATCH 1/4] implemented ability to add custom locales change was relatively small so added it directly to localService anything dealing with locales should use this service and not directly with DatabaseService.GetTables().Locales.Global as injected locales will be lost due to lazyloading --- .../Services/CustomLocaleService.cs | 75 --------------- .../Services/LocaleService.cs | 94 +++++++++++++++++-- 2 files changed, 85 insertions(+), 84 deletions(-) delete mode 100644 Libraries/SPTarkov.Server.Core/Services/CustomLocaleService.cs 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/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; + } } From eed3c5e105e1527c04b2975b13e237a2d7ebb8c3 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 17 Apr 2025 13:20:14 +0100 Subject: [PATCH 2/4] added and fixed spelling on comments --- .../SPTarkov.Server.Core/Models/Spt/Server/LocaleBase.cs | 2 ++ SPTarkov.Server/Program.cs | 9 +++------ 2 files changed, 5 insertions(+), 6 deletions(-) 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/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; } } From 257c8cc698c9dd924c56d7124d40cf9c6446128b Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 17 Apr 2025 13:20:54 +0100 Subject: [PATCH 3/4] moved removal of prapor message to localService as this needs to happen each time we lazyload --- .../Services/PostDbLoadService.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) 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 /// From 69ecde139ea7728e6d6f039a9ee15c9a62a5d076 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 17 Apr 2025 13:21:38 +0100 Subject: [PATCH 4/4] Update uasges to use LocalService --- .../Callbacks/DataCallbacks.cs | 8 ++++---- .../SptCommands/GiveCommand/GiveSptCommand.cs | 6 ++---- .../Services/I18nService.cs | 16 +++++++++++----- .../Services/LocalisationService.cs | 5 ++--- .../Services/Mod/CustomItemService.cs | 17 +++++------------ .../Services/SeasonalEventService.cs | 8 ++++---- 6 files changed, 28 insertions(+), 32 deletions(-) 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/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/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/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"); } ///