diff --git a/configs/addons/counterstrikesharp/lang/en.json b/configs/addons/counterstrikesharp/lang/en.json new file mode 100644 index 000000000..3b1b083c7 --- /dev/null +++ b/configs/addons/counterstrikesharp/lang/en.json @@ -0,0 +1,5 @@ +{ + "menu.button.previous": "Prev", + "menu.button.next": "Next", + "menu.button.close": "Close" +} diff --git a/managed/CounterStrikeSharp.API/Bootstrap.cs b/managed/CounterStrikeSharp.API/Bootstrap.cs index 17f3b1ec9..6148da026 100644 --- a/managed/CounterStrikeSharp.API/Bootstrap.cs +++ b/managed/CounterStrikeSharp.API/Bootstrap.cs @@ -12,7 +12,9 @@ using CounterStrikeSharp.API.Core.Translations; using CounterStrikeSharp.API.Modules.Admin; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Serilog; @@ -46,6 +48,10 @@ public static int Run() services.AddScoped(); services.AddSingleton(); + services.TryAddSingleton(); + services.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>)); + services.TryAddTransient(typeof(IStringLocalizer), typeof(StringLocalizer)); + services.Scan(i => i.FromCallingAssembly() .AddClasses(c => c.AssignableTo()) .AsSelfWithInterfaces() @@ -71,4 +77,4 @@ public static int Run() return 0; } } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Core/Application.cs b/managed/CounterStrikeSharp.API/Core/Application.cs index bd948014f..b3015cec5 100644 --- a/managed/CounterStrikeSharp.API/Core/Application.cs +++ b/managed/CounterStrikeSharp.API/Core/Application.cs @@ -28,6 +28,7 @@ using CounterStrikeSharp.API.Modules.Menu; using CounterStrikeSharp.API.Modules.Utils; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; namespace CounterStrikeSharp.API.Core @@ -39,6 +40,8 @@ public sealed class Application public static Application Instance => _instance!; + public static IStringLocalizer Localizer => Instance._localizer; + public static string RootDirectory => Instance._scriptHostConfiguration.RootPath; private readonly IScriptHostConfiguration _scriptHostConfiguration; @@ -48,11 +51,12 @@ public sealed class Application private readonly IPluginContextQueryHandler _pluginContextQueryHandler; private readonly IPlayerLanguageManager _playerLanguageManager; private readonly ICommandManager _commandManager; + private readonly IStringLocalizer _localizer; public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration, GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager, IPluginContextQueryHandler pluginContextQueryHandler, IPlayerLanguageManager playerLanguageManager, - ICommandManager commandManager) + ICommandManager commandManager, IStringLocalizer localizer) { Logger = loggerFactory.CreateLogger("Core"); _scriptHostConfiguration = scriptHostConfiguration; @@ -62,6 +66,7 @@ public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration script _pluginContextQueryHandler = pluginContextQueryHandler; _playerLanguageManager = playerLanguageManager; _commandManager = commandManager; + _localizer = localizer; _instance = this; } diff --git a/managed/CounterStrikeSharp.API/Core/Hosting/IScriptHostConfiguration.cs b/managed/CounterStrikeSharp.API/Core/Hosting/IScriptHostConfiguration.cs index 946f96f64..f409d8e9a 100644 --- a/managed/CounterStrikeSharp.API/Core/Hosting/IScriptHostConfiguration.cs +++ b/managed/CounterStrikeSharp.API/Core/Hosting/IScriptHostConfiguration.cs @@ -1,6 +1,5 @@ namespace CounterStrikeSharp.API.Core.Hosting; - /// /// Provides information about the CounterStrikeSharp host configuration. /// @@ -11,28 +10,34 @@ public interface IScriptHostConfiguration /// e.g. /game/csgo/addons/counterstrikesharp /// string RootPath { get; } - + /// /// Gets the absolute path to the directory that contains CounterStrikeSharp plugins. /// e.g. /game/csgo/addons/counterstrikesharp/plugins /// string PluginPath { get; } - + /// /// Gets the absolute path to the directory that contains CounterStrikeSharp plugin shared APIS. /// e.g. /game/csgo/addons/counterstrikesharp/shared /// string SharedPath { get; } - + /// /// Gets the absolute path to the directory that contains CounterStrikeSharp configs. /// e.g. /game/csgo/addons/counterstrikesharp/configs /// string ConfigsPath { get; } - + /// /// Gets the absolute path to the directory that contains CounterStrikeSharp game data. /// e.g. /game/csgo/addons/counterstrikesharp/gamedata /// string GameDataPath { get; } -} \ No newline at end of file + + /// + /// Gets the absolute path to the directory that contains CounterStrikeSharp translation files. + /// e.g. /game/csgo/addons/counterstrikesharp/lang + /// + string LanguagePath { get; } +} diff --git a/managed/CounterStrikeSharp.API/Core/Hosting/ScriptHostConfiguration.cs b/managed/CounterStrikeSharp.API/Core/Hosting/ScriptHostConfiguration.cs index c142a5327..4ffb86d7c 100644 --- a/managed/CounterStrikeSharp.API/Core/Hosting/ScriptHostConfiguration.cs +++ b/managed/CounterStrikeSharp.API/Core/Hosting/ScriptHostConfiguration.cs @@ -10,6 +10,7 @@ internal sealed class ScriptHostConfiguration : IScriptHostConfiguration public string SharedPath { get; } public string ConfigsPath { get; } public string GameDataPath { get; } + public string LanguagePath { get; } public ScriptHostConfiguration(IHostEnvironment hostEnvironment) { @@ -18,5 +19,6 @@ public ScriptHostConfiguration(IHostEnvironment hostEnvironment) PluginPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "plugins" }); ConfigsPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "configs" }); GameDataPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "gamedata" }); + LanguagePath = Path.Join(new[] { hostEnvironment.ContentRootPath, "lang" }); } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Core/Translations/CoreJsonStringLocalizerFactory.cs b/managed/CounterStrikeSharp.API/Core/Translations/CoreJsonStringLocalizerFactory.cs new file mode 100644 index 000000000..dfcdfaf3f --- /dev/null +++ b/managed/CounterStrikeSharp.API/Core/Translations/CoreJsonStringLocalizerFactory.cs @@ -0,0 +1,24 @@ +using CounterStrikeSharp.API.Core.Hosting; +using Microsoft.Extensions.Localization; + +namespace CounterStrikeSharp.API.Core.Translations; + +public class CoreJsonStringLocalizerFactory : IStringLocalizerFactory +{ + private IScriptHostConfiguration _scriptHostConfiguration; + + public CoreJsonStringLocalizerFactory(IScriptHostConfiguration scriptHostConfiguration) + { + _scriptHostConfiguration = scriptHostConfiguration; + } + + public IStringLocalizer Create(Type resourceSource) + { + return new JsonStringLocalizer(_scriptHostConfiguration.LanguagePath); + } + + public IStringLocalizer Create(string baseName, string location) + { + return new JsonStringLocalizer(_scriptHostConfiguration.LanguagePath); + } +} diff --git a/managed/CounterStrikeSharp.API/Core/Translations/JsonStringLocalizer.cs b/managed/CounterStrikeSharp.API/Core/Translations/JsonStringLocalizer.cs index 7a6934a02..ab6dcf715 100644 --- a/managed/CounterStrikeSharp.API/Core/Translations/JsonStringLocalizer.cs +++ b/managed/CounterStrikeSharp.API/Core/Translations/JsonStringLocalizer.cs @@ -10,13 +10,13 @@ public class JsonStringLocalizer : IStringLocalizer { private readonly JsonResourceManager _resourceManager; private readonly JsonStringProvider _resourceStringProvider; - + public JsonStringLocalizer(string langPath) { _resourceManager = new JsonResourceManager(langPath); _resourceStringProvider = new(_resourceManager); } - + public IEnumerable GetAllStrings(bool includeParentCultures) { return GetAllStrings(includeParentCultures, CultureInfo.CurrentUICulture); @@ -52,7 +52,7 @@ public LocalizedString this[string name] return new LocalizedString(name, value, resourceNotFound: format == null); } } - + protected string? GetStringSafely(string name, CultureInfo? culture = null) { if (name == null) @@ -69,16 +69,22 @@ public LocalizedString this[string name] { result = _resourceManager.GetFallbackString(name); } - - // Fallback to the default culture (en-US) if the resource is not found for the current culture. + + // Fallback to the default culture (whatever is in core.json) if the resource is not found for the current culture. if (result == null && !culture.Equals(CultureInfo.DefaultThreadCurrentUICulture)) { result = _resourceManager.GetString(name, CultureInfo.DefaultThreadCurrentUICulture!); } + // Fallback to the default culture (en) if the resource is not found for the current culture. + if (result == null && !culture.Equals(CultureInfo.InvariantCulture)) + { + result = _resourceManager.GetString(name, new CultureInfo("en")); + } + return result?.ReplaceColorTags(); } - + protected virtual IEnumerable GetAllStrings(bool includeParentCultures, CultureInfo culture) { if (culture == null) @@ -96,7 +102,7 @@ protected virtual IEnumerable GetAllStrings(bool includeParentC yield return new LocalizedString(name, value ?? name, resourceNotFound: value == null); } } - + private IEnumerable GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture) { var currentCulture = startingCulture; @@ -119,4 +125,4 @@ private IEnumerable GetResourceNamesFromCultureHierarchy(CultureInfo sta return resourceNames; } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Menu/CenterHtmlMenu.cs b/managed/CounterStrikeSharp.API/Modules/Menu/CenterHtmlMenu.cs index 9dc3e63ba..2eef384d7 100644 --- a/managed/CounterStrikeSharp.API/Modules/Menu/CenterHtmlMenu.cs +++ b/managed/CounterStrikeSharp.API/Modules/Menu/CenterHtmlMenu.cs @@ -33,7 +33,7 @@ public CenterHtmlMenu(string title, BasePlugin plugin) : base(ModifyTitle(title) { _plugin = plugin; } - + [Obsolete("Use the constructor that takes a BasePlugin")] public CenterHtmlMenu(string title) : base(ModifyTitle(title)) { @@ -45,8 +45,8 @@ public override void Open(CCSPlayerController player) { throw new InvalidOperationException("This method is unsupported with the CenterHtmlMenu constructor used." + "Please provide a BasePlugin in the constructor."); - }; - + } + MenuManager.OpenCenterHtmlMenu(_plugin, player, this); } @@ -123,19 +123,20 @@ public override void Display() if (HasPrevButton) { - builder.AppendFormat($"!7 <- Prev"); + builder.AppendFormat( + $"!7 <- {Application.Localizer["menu.button.previous"]}"); builder.AppendLine("
"); } if (HasNextButton) { - builder.AppendFormat($"!8 -> Next"); + builder.AppendFormat($"!8 -> {Application.Localizer["menu.button.next"]}"); builder.AppendLine("
"); } if (centerHtmlMenu.ExitButton) { - builder.AppendFormat($"!9 -> Close"); + builder.AppendFormat($"!9 -> {Application.Localizer["menu.button.close"]}"); builder.AppendLine("
"); } @@ -157,4 +158,4 @@ private void RemoveOnTickListener() var onTick = new Core.Listeners.OnTick(Display); _plugin.RemoveListener("OnTick", onTick); } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Menu/ChatMenu.cs b/managed/CounterStrikeSharp.API/Modules/Menu/ChatMenu.cs index 7d8e4e770..3f63c96eb 100644 --- a/managed/CounterStrikeSharp.API/Modules/Menu/ChatMenu.cs +++ b/managed/CounterStrikeSharp.API/Modules/Menu/ChatMenu.cs @@ -26,11 +26,12 @@ public class ChatMenu : BaseMenu public char PrevPageColor { get; set; } = ChatColors.Yellow; public char NextPageColor { get; set; } = ChatColors.Yellow; public char CloseColor { get; set; } = ChatColors.Red; + public ChatMenu(string title) : base(title) { ExitButton = false; } - + public override void Open(CCSPlayerController player) { MenuManager.OpenChatMenu(player, this); @@ -45,9 +46,9 @@ public ChatMenuInstance(CCSPlayerController player, ChatMenu menu) : base(player public override void Display() { - if (Menu is not ChatMenu chatMenu) - { - return; + if (Menu is not ChatMenu chatMenu) + { + return; } Player.PrintToChat($" {chatMenu.TitleColor} {chatMenu.Title}"); @@ -63,21 +64,21 @@ public override void Display() if (HasPrevButton) { - Player.PrintToChat($" {chatMenu.PrevPageColor}!7 {ChatColors.Default}-> Prev"); + Player.PrintToChat($" {chatMenu.PrevPageColor}!7 {ChatColors.Default}-> {Application.Localizer["menu.button.previous"]}"); } - + if (HasNextButton) { - Player.PrintToChat($" {chatMenu.NextPageColor}!8 {ChatColors.Default}-> Next"); + Player.PrintToChat($" {chatMenu.NextPageColor}!8 {ChatColors.Default}-> {Application.Localizer["menu.button.next"]}"); } if (Menu.ExitButton) { - Player.PrintToChat($" {chatMenu.CloseColor}!9 {ChatColors.Default}-> Close"); + Player.PrintToChat($" {chatMenu.CloseColor}!9 {ChatColors.Default}-> {Application.Localizer["menu.button.close"]}"); } } } - + public static class ChatMenus { [Obsolete("Use MenuManager.OpenChatMenu instead")] @@ -91,4 +92,4 @@ public static void OnKeyPress(CCSPlayerController player, int key) { MenuManager.OnKeyPress(player, key); } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Menu/ConsoleMenu.cs b/managed/CounterStrikeSharp.API/Modules/Menu/ConsoleMenu.cs index 65e4f048d..2a82dc871 100644 --- a/managed/CounterStrikeSharp.API/Modules/Menu/ConsoleMenu.cs +++ b/managed/CounterStrikeSharp.API/Modules/Menu/ConsoleMenu.cs @@ -49,20 +49,20 @@ public override void Display() Player.PrintToConsole($"{(option.Disabled ? "[Disabled] - " : "[Enabled]")} css_{keyOffset++} {option.Text}"); } - + if (HasPrevButton) { - Player.PrintToConsole("css_7 -> Prev"); + Player.PrintToConsole($"css_7 -> {Application.Localizer["menu.button.previous"]}"); } if (HasNextButton) { - Player.PrintToConsole("css_8 -> Next"); + Player.PrintToConsole($"css_8 -> {Application.Localizer["menu.button.next"]}"); } - + if (Menu.ExitButton) { - Player.PrintToConsole("css_9 -> Close"); + Player.PrintToConsole($"css_9 -> {Application.Localizer["menu.button.close"]}"); } } -} \ No newline at end of file +}