using System.Globalization; using SPTarkov.Common.Annotations; using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; namespace SPTarkov.Server.Core.Services; [Injectable(InjectionType.Singleton)] public class LocaleService( ISptLogger _logger, DatabaseServer _databaseServer, 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(); /// /// 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 of locales for desired language - en/fr/cn public Dictionary GetLocaleDb(string? language = null) { 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)) { // TODO: need to see if this needs to be cloned return RemovePraporTestMessage(localeToReturn); } throw new Exception($"unable to get locales from either {languageToUse} or en"); } /// /// 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); } /// /// Gets the game locale key from the locale.json file, /// if value is 'system' get system locale /// /// Locale e.g en/ge/cz/cn public string GetDesiredGameLocale() { if (_localeConfig.GameLocale.ToLower() == "system") { return GetPlatformForClientLocale(); } return _localeConfig.GameLocale.ToLower(); } /// /// Gets the game locale key from the locale.json file, /// if value is 'system' get system locale /// /// Locale e.g en/ge/cz/cn public string GetDesiredServerLocale() { if (_localeConfig.ServerLocale.ToLower() == "system") { return GetPlatformForServerLocale(); } return _localeConfig.ServerLocale.ToLower(); } /// /// Get array of languages supported for localisation /// /// List of locales e.g. en/fr/cn public List GetServerSupportedLocales() { return _localeConfig.ServerSupportedLocales; } /// /// Get array of languages supported for localisation /// /// Dictionary of locales e.g. en/fr/cn public Dictionary GetLocaleFallbacks() { return _localeConfig.Fallbacks; } /// /// Get the full locale of the computer running the server lowercased e.g. en-gb / pt-pt /// /// System locale as String public string GetPlatformForServerLocale() { var platformLocale = GetPlatformLocale(); if (platformLocale == null) { _logger.Warning("System language could not be found, falling back to english"); return "en"; } var baseNameCode = platformLocale.TwoLetterISOLanguageName.ToLower(); if (!_localeConfig.ServerSupportedLocales.Contains(baseNameCode)) { // Check if base language (e.g. CN / EN / DE) exists var languageCode = platformLocale.Name.ToLower(); if (_localeConfig.ServerSupportedLocales.Contains(languageCode)) { if (baseNameCode == "zh") // Handle edge case of zh { return "zh-cn"; } return languageCode; } if (baseNameCode == "pt") // Handle edge case of pt { return "pt-pt"; } _logger.Warning($"Unsupported system language found: {baseNameCode}, falling back to english"); return "en"; } return baseNameCode; } /// /// Get the locale of the computer running the server /// /// Language part of locale e.g. 'en' part of 'en-US' protected string GetPlatformForClientLocale() { var platformLocale = GetPlatformLocale(); if (platformLocale == null) { _logger.Warning("System language could not be found, falling back to english"); return "en"; } var locales = _databaseServer.GetTables().Locales; var baseNameCode = platformLocale.TwoLetterISOLanguageName.ToLower(); if (locales.Global.ContainsKey(baseNameCode)) { return baseNameCode; } var languageCode = platformLocale.Name.ToLower(); if (locales.Global.ContainsKey(languageCode)) { return languageCode; } // // const regionCode = platformLocale.region?.toLocaleLowerCase(); // if (regionCode && locales.global[regionCode]) { // return regionCode; // } // BSG map DE to GE some reason if (baseNameCode == "de") { return "ge"; } _logger.Warning($"Unsupported system language found: {languageCode}, falling back to english"); return "en"; } /// /// This is in a function so we can overwrite it during testing /// /// The current platform locale protected CultureInfo GetPlatformLocale() { return CultureInfo.InstalledUICulture; } public List GetLocaleKeysThatStartsWithValue(string partialKey) { 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; } }