Merge branch 'main' of https://github.com/sp-tarkov/server-csharp
This commit is contained in:
@@ -1,6 +1,107 @@
|
||||
namespace Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
|
||||
namespace Core.Models.Enums;
|
||||
|
||||
public static class ConfigTypesExtension
|
||||
{
|
||||
public static string GetValue(this ConfigTypes type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ConfigTypes.AIRDROP => "spt-airdrop",
|
||||
ConfigTypes.BACKUP => "spt-backup",
|
||||
ConfigTypes.BOT => "spt-bot",
|
||||
ConfigTypes.PMC => "spt-pmc",
|
||||
ConfigTypes.CORE => "spt-core",
|
||||
ConfigTypes.HEALTH => "spt-health",
|
||||
ConfigTypes.HIDEOUT => "spt-hideout",
|
||||
ConfigTypes.HTTP => "spt-http",
|
||||
ConfigTypes.IN_RAID => "spt-inraid",
|
||||
ConfigTypes.INSURANCE => "spt-insurance",
|
||||
ConfigTypes.INVENTORY => "spt-inventory",
|
||||
ConfigTypes.ITEM => "spt-item",
|
||||
ConfigTypes.LOCALE => "spt-locale",
|
||||
ConfigTypes.LOCATION => "spt-location",
|
||||
ConfigTypes.LOOT => "spt-loot",
|
||||
ConfigTypes.MATCH => "spt-match",
|
||||
ConfigTypes.PLAYERSCAV => "spt-playerscav",
|
||||
ConfigTypes.PMC_CHAT_RESPONSE => "spt-pmcchatresponse",
|
||||
ConfigTypes.QUEST => "spt-quest",
|
||||
ConfigTypes.RAGFAIR => "spt-ragfair",
|
||||
ConfigTypes.REPAIR => "spt-repair",
|
||||
ConfigTypes.SCAVCASE => "spt-scavcase",
|
||||
ConfigTypes.TRADER => "spt-trader",
|
||||
ConfigTypes.WEATHER => "spt-weather",
|
||||
ConfigTypes.SEASONAL_EVENT => "spt-seasonalevents",
|
||||
ConfigTypes.LOST_ON_DEATH => "spt-lostondeath",
|
||||
ConfigTypes.GIFTS => "spt-gifts",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static Type GetConfigType(this ConfigTypes type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ConfigTypes.AIRDROP => typeof(AirdropConfig),
|
||||
ConfigTypes.BACKUP => typeof(BackupConfig),
|
||||
ConfigTypes.BOT => typeof(BotConfig),
|
||||
ConfigTypes.PMC => typeof(PmcConfig),
|
||||
ConfigTypes.CORE => typeof(CoreConfig),
|
||||
ConfigTypes.HEALTH => typeof(HealthConfig),
|
||||
ConfigTypes.HIDEOUT => typeof(HideoutConfig),
|
||||
ConfigTypes.HTTP => typeof(HttpConfig),
|
||||
ConfigTypes.IN_RAID => typeof(InRaidConfig),
|
||||
ConfigTypes.INSURANCE => typeof(InsuranceConfig),
|
||||
ConfigTypes.INVENTORY => typeof(InventoryConfig),
|
||||
ConfigTypes.ITEM => typeof(ItemConfig),
|
||||
ConfigTypes.LOCALE => typeof(LocaleConfig),
|
||||
ConfigTypes.LOCATION => typeof(LocationConfig),
|
||||
ConfigTypes.LOOT => typeof(LootConfig),
|
||||
ConfigTypes.MATCH => typeof(MatchConfig),
|
||||
ConfigTypes.PLAYERSCAV => typeof(PlayerScavConfig),
|
||||
ConfigTypes.PMC_CHAT_RESPONSE => typeof(PmcChatResponse),
|
||||
ConfigTypes.QUEST => typeof(QuestConfig),
|
||||
ConfigTypes.RAGFAIR => typeof(RagfairConfig),
|
||||
ConfigTypes.REPAIR => typeof(RepairConfig),
|
||||
ConfigTypes.SCAVCASE => typeof(ScavCaseConfig),
|
||||
ConfigTypes.TRADER => typeof(TraderConfig),
|
||||
ConfigTypes.WEATHER => typeof(WeatherConfig),
|
||||
ConfigTypes.SEASONAL_EVENT => typeof(SeasonalEventConfig),
|
||||
ConfigTypes.LOST_ON_DEATH => typeof(LostOnDeathConfig),
|
||||
ConfigTypes.GIFTS => typeof(GiftsConfig),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConfigTypes
|
||||
{
|
||||
|
||||
AIRDROP,
|
||||
BACKUP,
|
||||
BOT,
|
||||
PMC,
|
||||
CORE,
|
||||
HEALTH,
|
||||
HIDEOUT,
|
||||
HTTP,
|
||||
IN_RAID,
|
||||
INSURANCE,
|
||||
INVENTORY,
|
||||
ITEM,
|
||||
LOCALE,
|
||||
LOCATION,
|
||||
LOOT,
|
||||
MATCH,
|
||||
PLAYERSCAV,
|
||||
PMC_CHAT_RESPONSE,
|
||||
QUEST,
|
||||
RAGFAIR,
|
||||
REPAIR,
|
||||
SCAVCASE,
|
||||
TRADER,
|
||||
WEATHER,
|
||||
SEASONAL_EVENT,
|
||||
LOST_ON_DEATH,
|
||||
GIFTS
|
||||
}
|
||||
@@ -6,6 +6,15 @@ using Core.Models.Spt.Dialog;
|
||||
|
||||
namespace Core.Models.Spt.Config;
|
||||
|
||||
public class GiftsConfig : BaseConfig
|
||||
{
|
||||
[JsonPropertyName("kind")]
|
||||
public string Kind { get; set; } = "spt-gifts";
|
||||
|
||||
[JsonPropertyName("gifts")]
|
||||
public Dictionary<string, Gift> Gifts { get; set; }
|
||||
}
|
||||
|
||||
public class Gift
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Core.Models.Spt.Config;
|
||||
|
||||
public class MatchConfig
|
||||
public class MatchConfig : BaseConfig
|
||||
{
|
||||
[JsonPropertyName("kind")]
|
||||
public string Kind { get; set; } = "spt-match";
|
||||
|
||||
@@ -1,8 +1,84 @@
|
||||
using Core.Annotations;
|
||||
using System.Runtime.InteropServices.JavaScript;
|
||||
using System.Text.Json;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Servers;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class ConfigServer
|
||||
{
|
||||
private ILogger _logger;
|
||||
protected Dictionary<string, object> configs;
|
||||
protected readonly string[] acceptableFileExtensions = ["json", "jsonc"];
|
||||
|
||||
public ConfigServer(
|
||||
ILogger logger
|
||||
// TODO: We need JsonUtil here => JsonUtil jsonUtil,
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public T GetConfig<T>(ConfigTypes configType) where T : BaseConfig
|
||||
{
|
||||
if (!configs.ContainsKey(configType.GetValue()))
|
||||
{
|
||||
throw new Exception($"Config: {configType} is undefined. Ensure you have not broken it via editing");
|
||||
}
|
||||
|
||||
return configs[configType.GetValue()] as T;
|
||||
}
|
||||
|
||||
public T GetConfigByString<T>(string configType) where T : BaseConfig
|
||||
{
|
||||
return configs[configType] as T;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_logger.Debug("Importing configs...");
|
||||
|
||||
// Get all filepaths
|
||||
var filepath = "./assets/configs/";
|
||||
var files = Directory.GetFiles(filepath);
|
||||
|
||||
// Add file content to result
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (acceptableFileExtensions.Contains(Path.GetExtension(file)))
|
||||
{
|
||||
var fileContent = File.ReadAllText(file);
|
||||
var type = GetConfigTypeByFilename(file);
|
||||
var deserializedContent = JsonSerializer.Deserialize(fileContent, type);
|
||||
|
||||
if (deserializedContent == null)
|
||||
{
|
||||
_logger.Error($"Config file: {file} is corrupt. Use a site like: https://jsonlint.com to find the issue.");
|
||||
throw new Exception($"Server will not run until the: {file} config error mentioned above is fixed");
|
||||
}
|
||||
|
||||
this.configs[$"spt-{Path.GetFileNameWithoutExtension(file)}"] = deserializedContent;
|
||||
}
|
||||
}
|
||||
|
||||
/** TODO: deal with this:
|
||||
this.logger.info(`Commit hash: ${
|
||||
globalThis.G_COMMIT || "DEBUG"
|
||||
}`);
|
||||
this.logger.info(`Build date: ${
|
||||
globalThis.G_BUILDTIME || "DEBUG"
|
||||
}`);
|
||||
**/
|
||||
}
|
||||
|
||||
private Type GetConfigTypeByFilename(string filename)
|
||||
{
|
||||
var type = Enum.GetValues<ConfigTypes>()
|
||||
.First(en => en.GetValue().Contains(Path.GetFileNameWithoutExtension(filename)));
|
||||
return type.GetConfigType();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
namespace Core.Servers;
|
||||
using Core.Models.Spt.Server;
|
||||
|
||||
namespace Core.Servers;
|
||||
|
||||
public class DatabaseServer
|
||||
{
|
||||
protected DatabaseTables tableData = new();
|
||||
|
||||
public DatabaseTables GetTables() => tableData;
|
||||
|
||||
public void SetTables(DatabaseTables tables) => tableData = tables;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Net.WebSockets;
|
||||
using Core.Context;
|
||||
using Core.Models.Config;
|
||||
using Core.Servers.Http;
|
||||
using Core.Services;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Servers;
|
||||
@@ -38,8 +39,7 @@ public class HttpServer
|
||||
_webSocketServer = webSocketServer;
|
||||
_httpListeners = httpListeners;
|
||||
|
||||
// TODO: hook up the config server to put the HttpConfig here
|
||||
httpConfig = new HttpConfig() { Ip = "127.0.0.1", Port = 80, LogRequests = true};
|
||||
httpConfig = _configServer.GetConfig<HttpConfig>(ConfigTypes.HTTP);
|
||||
}
|
||||
|
||||
public void Load(WebApplicationBuilder builder)
|
||||
@@ -89,13 +89,13 @@ public class HttpServer
|
||||
var isLocalRequest = IsLocalRequest(clientIp);
|
||||
if (isLocalRequest.HasValue) {
|
||||
if (isLocalRequest.Value) {
|
||||
_logger.Info(_localisationService.GetText("client_request", req.url));
|
||||
} else {
|
||||
_logger.Info(_localisationService.GetText("client_request", context.Request.Path.Value));
|
||||
} else
|
||||
{
|
||||
_logger.Info(
|
||||
_localisationService.GetText("client_request_ip", {
|
||||
ip: clientIp,
|
||||
url: req.url.replaceAll("/", "\\"), // Localisation service escapes `/` into hex code `/`
|
||||
}),
|
||||
_localisationService.GetText("client_request_ip",
|
||||
new Dictionary<string, string>
|
||||
{ { "ip", clientIp }, { "url", context.Request.Path.Value } })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Core.Services;
|
||||
|
||||
public class I18nService
|
||||
{
|
||||
private string[] _locales;
|
||||
private List<string> _locales;
|
||||
private Dictionary<string, string> _fallbacks;
|
||||
private string _defaultLocale;
|
||||
private string _directory;
|
||||
@@ -13,7 +13,7 @@ public class I18nService
|
||||
|
||||
private Dictionary<string, Dictionary<string, string>> _loadedLocales = new();
|
||||
|
||||
public I18nService(string[] locales, Dictionary<string, string> fallbacks, string defaultLocale, string directory)
|
||||
public I18nService(List<string> locales, Dictionary<string, string> fallbacks, string defaultLocale, string directory)
|
||||
{
|
||||
_locales = locales;
|
||||
_fallbacks = fallbacks;
|
||||
@@ -30,9 +30,12 @@ public class I18nService
|
||||
throw new Exception($"Localisation files in directory {_directory} not found.");
|
||||
foreach (var file in files)
|
||||
{
|
||||
_loadedLocales.Add(Path.GetFileName(file),
|
||||
_loadedLocales.Add(Path.GetFileNameWithoutExtension(file),
|
||||
JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(file)) ?? new Dictionary<string, string>());
|
||||
}
|
||||
|
||||
if (!_loadedLocales.ContainsKey(_defaultLocale))
|
||||
throw new Exception($"The default locale '{_defaultLocale}' does not exist on the loaded locales.");
|
||||
}
|
||||
|
||||
public void SetLocale(string locale)
|
||||
@@ -49,16 +52,28 @@ public class I18nService
|
||||
throw new Exception($"Locale '{locale}' was not defined, and the found fallback locale did not match any of the loaded locales.");
|
||||
_setLocale = foundFallbackLocale;
|
||||
}
|
||||
|
||||
_setLocale = _defaultLocale;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetLocalised(string key)
|
||||
{
|
||||
return null;
|
||||
if (!_loadedLocales.TryGetValue(_setLocale, out var locales))
|
||||
return key;
|
||||
if (!locales.TryGetValue(key, out var value))
|
||||
{
|
||||
_loadedLocales.TryGetValue(_defaultLocale, out var defaults);
|
||||
defaults.TryGetValue(key, out value);
|
||||
return value ?? key;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public string GetLocalised(string key, params object[] args)
|
||||
{
|
||||
return null;
|
||||
// TODO: Deal with arguments
|
||||
return GetLocalised(key);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using Core.Annotations;
|
||||
using System.Globalization;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
@@ -7,6 +10,7 @@ namespace Core.Services;
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class LocaleService
|
||||
{
|
||||
private readonly LocaleConfig _localeConfig;
|
||||
private readonly ILogger _logger;
|
||||
private readonly DatabaseServer _databaseServer;
|
||||
private readonly ConfigServer _configServer;
|
||||
@@ -16,6 +20,7 @@ public class LocaleService
|
||||
_logger = logger;
|
||||
_databaseServer = databaseServer;
|
||||
_configServer = configServer;
|
||||
_localeConfig = configServer.GetConfig<LocaleConfig>(ConfigTypes.LOCALE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,18 +29,16 @@ public class LocaleService
|
||||
*/
|
||||
public Dictionary<string, string> GetLocaleDb()
|
||||
{
|
||||
/*
|
||||
const desiredLocale = this.databaseServer.getTables().locales.global[this.getDesiredGameLocale()];
|
||||
if (desiredLocale) {
|
||||
var desiredLocale = _databaseServer.GetTables().locales.Global[GetDesiredGameLocale()];
|
||||
if (desiredLocale != null)
|
||||
{
|
||||
return desiredLocale;
|
||||
}
|
||||
|
||||
this.logger.warning(
|
||||
`Unable to find desired locale file using locale: ${this.getDesiredGameLocale()} from config/locale.json, falling back to 'en'`,
|
||||
);
|
||||
_logger.Warning(
|
||||
$"Unable to find desired locale file using locale: {GetDesiredGameLocale()} from config/locale.json, falling back to 'en'");
|
||||
|
||||
return this.databaseServer.getTables().locales.global.en;
|
||||
*/
|
||||
return _databaseServer.GetTables().locales.Global["en"];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,13 +48,12 @@ public class LocaleService
|
||||
*/
|
||||
public string GetDesiredGameLocale()
|
||||
{
|
||||
/*
|
||||
if (this.localeConfig.gameLocale.toLowerCase() === "system") {
|
||||
return this.getPlatformForClientLocale();
|
||||
if (_localeConfig.GameLocale.ToLower() == "system")
|
||||
{
|
||||
return GetPlatformForClientLocale();
|
||||
}
|
||||
|
||||
return this.localeConfig.gameLocale.toLowerCase();
|
||||
*/
|
||||
return _localeConfig.GameLocale.ToLower();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,22 +63,21 @@ public class LocaleService
|
||||
*/
|
||||
public string GetDesiredServerLocale()
|
||||
{
|
||||
/*
|
||||
if (this.localeConfig.serverLocale.toLowerCase() === "system") {
|
||||
return this.getPlatformForServerLocale();
|
||||
if (_localeConfig.ServerLocale.ToLower() == "system")
|
||||
{
|
||||
return GetPlatformForServerLocale();
|
||||
}
|
||||
|
||||
return this.localeConfig.serverLocale.toLowerCase();
|
||||
*/
|
||||
return _localeConfig.ServerLocale.ToLower();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of languages supported for localisation
|
||||
* @returns array of locales e.g. en/fr/cn
|
||||
*/
|
||||
public ICollection<string> GetServerSupportedLocales()
|
||||
public List<string> GetServerSupportedLocales()
|
||||
{
|
||||
// return this.localeConfig.serverSupportedLocales;
|
||||
return _localeConfig.ServerSupportedLocales;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +86,7 @@ public class LocaleService
|
||||
*/
|
||||
public Dictionary<string, string> GetLocaleFallbacks()
|
||||
{
|
||||
// return this.localeConfig.fallbacks;
|
||||
return _localeConfig.Fallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,20 +95,22 @@ public class LocaleService
|
||||
*/
|
||||
public string GetPlatformForServerLocale()
|
||||
{
|
||||
/*
|
||||
const platformLocale = this.getPlatformLocale();
|
||||
if (!platformLocale) {
|
||||
this.logger.warning("System language could not be found, falling back to english");
|
||||
|
||||
var platformLocale = GetPlatformLocale();
|
||||
if (platformLocale == null)
|
||||
{
|
||||
_logger.Warning("System language could not be found, falling back to english");
|
||||
return "en";
|
||||
}
|
||||
|
||||
const baseNameCode = platformLocale.baseName.toLowerCase();
|
||||
if (!this.localeConfig.serverSupportedLocales.includes(baseNameCode)) {
|
||||
// Chek if base language (e.g. CN / EN / DE) exists
|
||||
const languageCode = platformLocale.language.toLocaleLowerCase();
|
||||
if (this.localeConfig.serverSupportedLocales.includes(languageCode)) {
|
||||
if (baseNameCode === "zh") {
|
||||
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";
|
||||
}
|
||||
@@ -115,18 +118,18 @@ public class LocaleService
|
||||
return languageCode;
|
||||
}
|
||||
|
||||
if (baseNameCode === "pt") {
|
||||
if (baseNameCode == "pt")
|
||||
{
|
||||
// Handle edge case of pt
|
||||
return "pt-pt";
|
||||
}
|
||||
|
||||
this.logger.warning(`Unsupported system language found: ${baseNameCode}, falling back to english`);
|
||||
_logger.Warning($"Unsupported system language found: {baseNameCode}, falling back to english");
|
||||
|
||||
return "en";
|
||||
}
|
||||
|
||||
return baseNameCode;
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,44 +138,49 @@ public class LocaleService
|
||||
*/
|
||||
protected string GetPlatformForClientLocale()
|
||||
{
|
||||
/*
|
||||
const platformLocale = this.getPlatformLocale();
|
||||
if (!platformLocale) {
|
||||
this.logger.warning("System language could not be found, falling back to english");
|
||||
var platformLocale = GetPlatformLocale();
|
||||
if (platformLocale == null)
|
||||
{
|
||||
_logger.Warning("System language could not be found, falling back to english");
|
||||
return "en";
|
||||
}
|
||||
|
||||
const locales = this.databaseServer.getTables().locales;
|
||||
const baseNameCode = platformLocale.baseName?.toLocaleLowerCase();
|
||||
if (baseNameCode && locales.global[baseNameCode]) {
|
||||
var locales = _databaseServer.GetTables().locales;
|
||||
var baseNameCode = platformLocale.TwoLetterISOLanguageName.ToLower();
|
||||
if (locales.Global.ContainsKey(baseNameCode))
|
||||
{
|
||||
return baseNameCode;
|
||||
}
|
||||
|
||||
const languageCode = platformLocale.language?.toLowerCase();
|
||||
if (languageCode && locales.global[languageCode]) {
|
||||
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 (platformLocale.language === "de") {
|
||||
if (baseNameCode == "de")
|
||||
{
|
||||
return "ge";
|
||||
}
|
||||
|
||||
this.logger.warning(`Unsupported system language found: ${languageCode}, falling back to english`);
|
||||
_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
|
||||
* @returns The current platform locale
|
||||
protected getPlatformLocale(): Intl.Locale {
|
||||
return new Intl.Locale(Intl.DateTimeFormat().resolvedOptions().locale);
|
||||
}
|
||||
*/
|
||||
protected CultureInfo GetPlatformLocale()
|
||||
{
|
||||
return CultureInfo.InstalledUICulture;
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,13 @@ public class LocalisationService
|
||||
_randomUtil = randomUtil;
|
||||
_databaseServer = databaseServer;
|
||||
_localeService = localeService;
|
||||
_i18nService = new I18nService();
|
||||
|
||||
File.ReadAllText()
|
||||
_i18nService = new I18nService(
|
||||
localeService.GetServerSupportedLocales(),
|
||||
localeService.GetLocaleFallbacks(),
|
||||
"en",
|
||||
"./Assets/database/locales/server"
|
||||
);
|
||||
_i18nService.SetLocale(localeService.GetDesiredServerLocale());
|
||||
}
|
||||
|
||||
public string GetText(string key, object? args = null)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using Core.Annotations;
|
||||
|
||||
namespace Core.Utils;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class JsonUtil
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user