mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-17 18:39:07 +00:00
Add per-player menu localization and refactor menus
Introduces per-player localization for menu categories and items using translation keys and IStringLocalizer, allowing modules and the main plugin to display menu names in the player's language. Refactors menu registration and builder logic to use translation keys, updates API and documentation, and adds database provider upsert query abstraction for player IPs. Also updates version to 1.7.8-beta-4 and corrects a translation string typo.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
@@ -205,6 +206,14 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
|
||||
}
|
||||
|
||||
public void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer)
|
||||
{
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryNameKey, permission, localizer);
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||
Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
@@ -263,6 +272,39 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuNameKey,
|
||||
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer)
|
||||
{
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuNameKey, BuilderFactory, permission, commandName, localizer);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
{
|
||||
var context = new MenuContext(categoryId, menuId, menuNameKey, permission, commandName);
|
||||
|
||||
if (menuFactory(player, context) is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
||||
|
||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
||||
menuBuilder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return menuBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UnregisterMenu(string categoryId, string menuId)
|
||||
{
|
||||
@@ -289,7 +331,30 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
|
||||
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
||||
{
|
||||
return CreateMenuWithBack(context.MenuTitle, context.CategoryId, player);
|
||||
// Get translated title if module has localizer
|
||||
string title = context.MenuTitle;
|
||||
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||
{
|
||||
// Check if this specific menu has a localizer
|
||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||
{
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
// Fallback to category localizer
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMenuWithBack(title, context.CategoryId, player);
|
||||
}
|
||||
|
||||
public List<CCSPlayerController> GetValidPlayers()
|
||||
@@ -321,7 +386,30 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||
{
|
||||
return CreateMenuWithPlayers(context.MenuTitle, context.CategoryId, admin, filter, onSelect);
|
||||
// Get translated title if module has localizer
|
||||
string title = context.MenuTitle;
|
||||
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||
{
|
||||
// Check if this specific menu has a localizer
|
||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||
{
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
// Fallback to category localizer
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMenuWithPlayers(title, context.CategoryId, admin, filter, onSelect);
|
||||
}
|
||||
|
||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
||||
|
||||
@@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
||||
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
||||
public override string ModuleAuthor => "daffyy & Dliix66";
|
||||
public override string ModuleVersion => "1.7.8-beta-3";
|
||||
public override string ModuleVersion => "1.7.8-beta-4";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,9 @@ public interface IDatabaseProvider
|
||||
string GetBanSelectQuery(bool multiServer);
|
||||
string GetIpHistoryQuery();
|
||||
string GetBanUpdateQuery(bool multiServer);
|
||||
|
||||
// PlayerManager
|
||||
string GetUpsertPlayerIpQuery();
|
||||
|
||||
// PermissionManager
|
||||
string GetAdminsQuery();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
||||
`steamid` INTEGER NOT NULL,
|
||||
`address` INTEGER NOT NULL
|
||||
`address` INTEGER NOT NULL,
|
||||
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`steamid`, `address`)
|
||||
);
|
||||
@@ -86,6 +86,17 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
|
||||
return "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
||||
}
|
||||
|
||||
public string GetUpsertPlayerIpQuery()
|
||||
{
|
||||
return """
|
||||
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
}
|
||||
|
||||
public string GetBanUpdateQuery(bool multiServer)
|
||||
{
|
||||
return multiServer ? """
|
||||
|
||||
@@ -83,6 +83,17 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider
|
||||
public string GetIpHistoryQuery() =>
|
||||
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
||||
|
||||
public string GetUpsertPlayerIpQuery()
|
||||
{
|
||||
return """
|
||||
INSERT INTO sa_players_ips (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(steamid, address) DO UPDATE SET
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
}
|
||||
|
||||
public string GetBanUpdateQuery(bool multiServer) =>
|
||||
multiServer
|
||||
? """
|
||||
|
||||
@@ -426,7 +426,7 @@ internal static class Helper
|
||||
|
||||
var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : "<https://steamcommunity.com/profiles/0>";
|
||||
var callerName = caller != null ? caller.PlayerName : CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
|
||||
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(Helper.GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString]));
|
||||
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString]));
|
||||
}
|
||||
|
||||
private static void SendDiscordLogMessage(CCSPlayerController? caller, string command, IStringLocalizer? localizer)
|
||||
@@ -1026,7 +1026,9 @@ public static class Time
|
||||
{
|
||||
public static DateTime ActualDateTime()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
if (CS2_SimpleAdmin.Instance.Config.DatabaseConfig.DatabaseType.ToLower().Equals("sqlite"))
|
||||
return DateTime.UtcNow;
|
||||
|
||||
string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone;
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -99,14 +99,8 @@ internal class PlayerManager
|
||||
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
|
||||
var ipUint = IpHelper.IpToUint(ipAddress);
|
||||
|
||||
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE pattern
|
||||
const string upsertQuery = """
|
||||
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
// Use database-specific UPSERT query (handles MySQL vs SQLite syntax differences)
|
||||
var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery();
|
||||
|
||||
await connection.ExecuteAsync(upsertQuery, new
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
@@ -12,32 +13,33 @@ public abstract class BasicMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes all menus in the system by registering them with the MenuManager.
|
||||
/// Register with translation keys instead of static names - translation happens per-player.
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
var manager = MenuManager.Instance;
|
||||
|
||||
// Players category menus
|
||||
manager.RegisterMenu("players", "slap", "Slap Player", CreateSlapMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "slay", "Slay Player", CreateSlayMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "warn", "Warn Player", CreateWarnMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
|
||||
manager.RegisterMenu("players", "gag", "Gag Player", CreateGagMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "mute", "Mute Player", CreateMuteMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "silence", "Silence Player", CreateSilenceMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "team", "Force Team", CreateForceTeamMenu, "@css/kick");
|
||||
// Players category menus - using translation keys
|
||||
manager.RegisterMenu("players", "slap", "sa_slap", CreateSlapMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "slay", "sa_slay", CreateSlayMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "kick", "sa_kick", CreateKickMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "warn", "sa_warn", CreateWarnMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "ban", "sa_ban", CreateBanMenu, "@css/ban");
|
||||
manager.RegisterMenu("players", "gag", "sa_gag", CreateGagMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "mute", "sa_mute", CreateMuteMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "silence", "sa_silence", CreateSilenceMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "team", "sa_team_force", CreateForceTeamMenu, "@css/kick");
|
||||
|
||||
// Server category menus
|
||||
manager.RegisterMenu("server", "plugins", "Manage Plugins", CreatePluginsMenu, "@css/root");
|
||||
manager.RegisterMenu("server", "changemap", "Change Map", CreateChangeMapMenu, "@css/changemap");
|
||||
manager.RegisterMenu("server", "restart", "Restart Game", CreateRestartGameMenu, "@css/generic");
|
||||
manager.RegisterMenu("server", "custom", "Custom Commands", CreateCustomCommandsMenu, "@css/generic");
|
||||
// Server category menus - using translation keys
|
||||
manager.RegisterMenu("server", "plugins", "sa_menu_pluginsmanager_title", CreatePluginsMenu, "@css/root");
|
||||
manager.RegisterMenu("server", "changemap", "sa_changemap", CreateChangeMapMenu, "@css/changemap");
|
||||
manager.RegisterMenu("server", "restart", "sa_restart_game", CreateRestartGameMenu, "@css/generic");
|
||||
manager.RegisterMenu("server", "custom", "sa_menu_custom_commands", CreateCustomCommandsMenu, "@css/generic");
|
||||
|
||||
// Admin category menus
|
||||
manager.RegisterMenu("admin", "add", "Add Admin", CreateAddAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "remove", "Remove Admin", CreateRemoveAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "reload", "Reload Admins", CreateReloadAdminsMenu, "@css/root");
|
||||
// Admin category menus - using translation keys
|
||||
manager.RegisterMenu("admin", "add", "sa_admin_add", CreateAddAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "remove", "sa_admin_remove", CreateRemoveAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "reload", "sa_admin_reload", CreateReloadAdminsMenu, "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,10 +51,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateSlapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slapMenu = new MenuBuilder(localizer?["sa_slap"] ?? "Slap Player");
|
||||
var slapMenu = new MenuBuilder("sa_slap", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -70,18 +72,25 @@ public abstract class BasicMenu
|
||||
/// <returns>A MenuBuilder instance for the slap damage menu.</returns>
|
||||
private static MenuBuilder CreateSlapDamageMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var slapDamageMenu = new MenuBuilder($"Slap: {target.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
string localizedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedTitle = $"{localizer?["sa_slap"] ?? "Slap"}: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var slapDamageMenu = new MenuBuilder(localizedTitle);
|
||||
var damages = new[] { 0, 1, 5, 10, 50, 100 };
|
||||
|
||||
foreach (var damage in damages)
|
||||
{
|
||||
slapDamageMenu.AddOption($"{damage} HP", _ =>
|
||||
slapDamageMenu.AddOption($"{damage} HP", currentAdmin =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Slap(admin, target, damage);
|
||||
// Keep menu open for consecutive slaps
|
||||
CreateSlapDamageMenu(admin, target).OpenMenu(admin);
|
||||
CS2_SimpleAdmin.Slap(currentAdmin, target, damage);
|
||||
// Reopen the same menu (not create new one) to keep back button working
|
||||
slapDamageMenu.OpenMenu(currentAdmin);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -97,10 +106,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateSlayMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slayMenu = new MenuBuilder(localizer?["sa_slay"] ?? "Slay Player");
|
||||
var slayMenu = new MenuBuilder("sa_slay", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -124,14 +133,14 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateKickMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var kickMenu = new MenuBuilder(localizer?["sa_kick"] ?? "Kick Player");
|
||||
var kickMenu = new MenuBuilder("sa_kick", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
|
||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
@@ -152,10 +161,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateWarnMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var warnMenu = new MenuBuilder(localizer?["sa_warn"] ?? "Warn Player");
|
||||
var warnMenu = new MenuBuilder("sa_warn", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -181,10 +190,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateBanMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var banMenu = new MenuBuilder(localizer?["sa_ban"] ?? "Ban Player");
|
||||
var banMenu = new MenuBuilder("sa_ban", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -210,10 +219,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateGagMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var gagMenu = new MenuBuilder(localizer?["sa_gag"] ?? "Gag Player");
|
||||
var gagMenu = new MenuBuilder("sa_gag", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -239,10 +248,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateMuteMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var muteMenu = new MenuBuilder(localizer?["sa_mute"] ?? "Mute Player");
|
||||
var muteMenu = new MenuBuilder("sa_mute", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -268,10 +277,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateSilenceMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var silenceMenu = new MenuBuilder(localizer?["sa_silence"] ?? "Silence Player");
|
||||
var silenceMenu = new MenuBuilder("sa_silence", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -297,10 +306,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateForceTeamMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamMenu = new MenuBuilder(localizer?["sa_team_force"] ?? "Force Team");
|
||||
var teamMenu = new MenuBuilder("sa_team_force", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -319,14 +328,32 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateTeamSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamSelectionMenu = new MenuBuilder($"Force Team: {target.PlayerName}");
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedTitle = $"{localizer?["sa_team_force"] ?? "Force Team"}: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var teamSelectionMenu = new MenuBuilder(localizedTitle);
|
||||
|
||||
// Localize team options for admin's language
|
||||
string ctName, tName, swapName, specName;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
ctName = localizer?["sa_team_ct"] ?? "CT";
|
||||
tName = localizer?["sa_team_t"] ?? "T";
|
||||
swapName = localizer?["sa_team_swap"] ?? "Swap";
|
||||
specName = localizer?["sa_team_spec"] ?? "Spec";
|
||||
}
|
||||
|
||||
var teams = new[]
|
||||
{
|
||||
(localizer?["sa_team_ct"] ?? "CT", "ct", CsTeam.CounterTerrorist),
|
||||
(localizer?["sa_team_t"] ?? "T", "t", CsTeam.Terrorist),
|
||||
(localizer?["sa_team_swap"] ?? "Swap", "swap", CsTeam.Spectator),
|
||||
(localizer?["sa_team_spec"] ?? "Spec", "spec", CsTeam.Spectator)
|
||||
(ctName, "ct", CsTeam.CounterTerrorist),
|
||||
(tName, "t", CsTeam.Terrorist),
|
||||
(swapName, "swap", CsTeam.Spectator),
|
||||
(specName, "spec", CsTeam.Spectator)
|
||||
};
|
||||
|
||||
foreach (var (name, teamName, teamNum) in teams)
|
||||
@@ -351,7 +378,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreatePluginsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var pluginsMenu = new MenuBuilder(localizer?["sa_menu_pluginsmanager_title"] ?? "Manage Plugins");
|
||||
var pluginsMenu = new MenuBuilder("sa_menu_pluginsmanager_title", admin, localizer);
|
||||
|
||||
pluginsMenu.AddOption("Open Plugins Manager", _ =>
|
||||
{
|
||||
@@ -369,7 +396,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateChangeMapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mapMenu = new MenuBuilder(localizer?["sa_changemap"] ?? "Change Map");
|
||||
var mapMenu = new MenuBuilder("sa_changemap", admin, localizer);
|
||||
|
||||
// Add default maps
|
||||
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
|
||||
@@ -402,7 +429,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateRestartGameMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var restartMenu = new MenuBuilder(localizer?["sa_restart_game"] ?? "Restart Game");
|
||||
var restartMenu = new MenuBuilder("sa_restart_game", admin, localizer);
|
||||
|
||||
restartMenu.AddOption("Restart Round", _ =>
|
||||
{
|
||||
@@ -420,7 +447,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateCustomCommandsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var customMenu = new MenuBuilder(localizer?["sa_menu_custom_commands"] ?? "Custom Commands");
|
||||
var customMenu = new MenuBuilder("sa_menu_custom_commands", admin, localizer);
|
||||
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
|
||||
@@ -455,10 +482,10 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateAddAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var addAdminMenu = new MenuBuilder(localizer?["sa_admin_add"] ?? "Add Admin");
|
||||
var addAdminMenu = new MenuBuilder("sa_admin_add", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
@@ -476,7 +503,16 @@ public abstract class BasicMenu
|
||||
/// <returns>A MenuBuilder instance for the admin flags menu.</returns>
|
||||
private static MenuBuilder CreateAdminFlagsMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var flagsMenu = new MenuBuilder($"Add Admin: {target.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedTitle = $"{localizer?["sa_admin_add"] ?? "Add Admin"}: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var flagsMenu = new MenuBuilder(localizedTitle);
|
||||
|
||||
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
|
||||
{
|
||||
@@ -501,7 +537,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateRemoveAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var removeAdminMenu = new MenuBuilder(localizer?["sa_admin_remove"] ?? "Remove Admin");
|
||||
var removeAdminMenu = new MenuBuilder("sa_admin_remove", admin, localizer);
|
||||
|
||||
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
||||
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
||||
@@ -531,7 +567,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateReloadAdminsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var reloadMenu = new MenuBuilder(localizer?["sa_admin_reload"] ?? "Reload Admins");
|
||||
var reloadMenu = new MenuBuilder("sa_admin_reload", admin, localizer);
|
||||
|
||||
reloadMenu.AddOption("Reload Admins", _ =>
|
||||
{
|
||||
@@ -546,13 +582,35 @@ public abstract class BasicMenu
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting duration.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
||||
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
||||
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||
{
|
||||
var durationMenu = new MenuBuilder($"{actionName} Duration: {player.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Convert action name to translation key (e.g., "Ban" -> "sa_ban")
|
||||
var actionKey = actionName.ToLower() switch
|
||||
{
|
||||
"kick" => "sa_kick",
|
||||
"ban" => "sa_ban",
|
||||
"warn" => "sa_warn",
|
||||
"gag" => "sa_gag",
|
||||
"mute" => "sa_mute",
|
||||
"silence" => "sa_silence",
|
||||
_ => actionName
|
||||
};
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedAction, durationText;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedAction = localizer?[actionKey] ?? actionName;
|
||||
durationText = localizer?["sa_duration"] ?? "Duration";
|
||||
}
|
||||
|
||||
var durationMenu = new MenuBuilder($"{localizedAction} {durationText}: {player.PlayerName}");
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
@@ -570,14 +628,36 @@ public abstract class BasicMenu
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting reason.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
||||
/// <param name="penaltyType">The type of penalty to determine which reason list to use.</param>
|
||||
/// <param name="onSelectAction">Callback action executed when reason is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the reason menu.</returns>
|
||||
private static MenuBuilder CreateReasonMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
PenaltyType penaltyType, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
|
||||
{
|
||||
var reasonMenu = new MenuBuilder($"{actionName} Reason: {player.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Convert action name to translation key
|
||||
var actionKey = actionName.ToLower() switch
|
||||
{
|
||||
"kick" => "sa_kick",
|
||||
"ban" => "sa_ban",
|
||||
"warn" => "sa_warn",
|
||||
"gag" => "sa_gag",
|
||||
"mute" => "sa_mute",
|
||||
"silence" => "sa_silence",
|
||||
_ => actionName
|
||||
};
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedAction, reasonText;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedAction = localizer?[actionKey] ?? actionName;
|
||||
reasonText = localizer?["sa_reason"] ?? "Reason";
|
||||
}
|
||||
|
||||
var reasonMenu = new MenuBuilder($"{localizedAction} {reasonText}: {player.PlayerName}");
|
||||
|
||||
var reasons = penaltyType switch
|
||||
{
|
||||
|
||||
@@ -1,28 +1,89 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
public class MenuBuilder(string title)
|
||||
public class MenuBuilder
|
||||
{
|
||||
private readonly string _title;
|
||||
private readonly CCSPlayerController? _player;
|
||||
private readonly IStringLocalizer? _localizer;
|
||||
private readonly List<MenuOption> _options = [];
|
||||
private MenuBuilder? _parentMenu;
|
||||
private Action<CCSPlayerController>? _backAction;
|
||||
private Action<CCSPlayerController>? _resetAction;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for player-localized menu with translation key
|
||||
/// </summary>
|
||||
public MenuBuilder(string titleKey, CCSPlayerController player, IStringLocalizer? localizer = null)
|
||||
{
|
||||
_title = titleKey;
|
||||
_player = player;
|
||||
_localizer = localizer ?? CS2_SimpleAdmin._localizer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for static title (backward compatibility)
|
||||
/// </summary>
|
||||
public MenuBuilder(string title)
|
||||
{
|
||||
_title = title;
|
||||
_player = null;
|
||||
_localizer = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localized title for the player
|
||||
/// </summary>
|
||||
private string GetLocalizedTitle()
|
||||
{
|
||||
if (_player != null && _localizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(_player.GetLanguage()))
|
||||
{
|
||||
return _localizer[_title];
|
||||
}
|
||||
}
|
||||
return _title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option with an action.
|
||||
/// </summary>
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)
|
||||
/// <param name="name">Display name or translation key</param>
|
||||
/// <param name="action">Action to perform when selected</param>
|
||||
/// <param name="disabled">Whether the option is disabled</param>
|
||||
/// <param name="permission">Required permission</param>
|
||||
/// <param name="isTranslationKey">If true, name is a translation key to be localized</param>
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null, bool isTranslationKey = false)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = action,
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
Permission = permission,
|
||||
IsTranslationKey = isTranslationKey
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localized name for a menu option
|
||||
/// </summary>
|
||||
private string GetLocalizedOptionName(MenuOption option)
|
||||
{
|
||||
if (option.IsTranslationKey && _player != null && _localizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(_player.GetLanguage()))
|
||||
{
|
||||
return _localizer[option.Name];
|
||||
}
|
||||
}
|
||||
return option.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option that opens a submenu.
|
||||
/// </summary>
|
||||
@@ -99,8 +160,11 @@ public class MenuBuilder(string title)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
|
||||
// Get localized title
|
||||
var localizedTitle = GetLocalizedTitle();
|
||||
|
||||
// Use MenuManager dependency
|
||||
var menu = Helper.CreateMenu(title, _backAction);
|
||||
var menu = Helper.CreateMenu(localizedTitle, _backAction);
|
||||
if (menu == null) return;
|
||||
|
||||
foreach (var option in _options)
|
||||
@@ -115,7 +179,10 @@ public class MenuBuilder(string title)
|
||||
}
|
||||
}
|
||||
|
||||
menu.AddMenuOption(option.Name, (menuPlayer, menuOption) =>
|
||||
// Get localized option name
|
||||
var localizedName = GetLocalizedOptionName(option);
|
||||
|
||||
menu.AddMenuOption(localizedName, (menuPlayer, menuOption) =>
|
||||
{
|
||||
option.Action?.Invoke(menuPlayer);
|
||||
}, option.Disabled);
|
||||
@@ -166,5 +233,6 @@ public class MenuOption
|
||||
public Action<CCSPlayerController>? Action { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string? Permission { get; set; }
|
||||
public bool IsTranslationKey { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
@@ -38,6 +39,26 @@ public class MenuManager
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new menu category with per-player localization support for modules.
|
||||
/// 🆕 NEW: Enables modules to provide localized category names based on each player's css_lang!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">Unique identifier for the category.</param>
|
||||
/// <param name="categoryNameKey">Translation key from module's lang files.</param>
|
||||
/// <param name="permission">Required permission to access this category.</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
public void RegisterCategory(string categoryId, string categoryNameKey, string permission, Microsoft.Extensions.Localization.IStringLocalizer moduleLocalizer)
|
||||
{
|
||||
_menuCategories[categoryId] = new MenuCategory
|
||||
{
|
||||
Id = categoryId,
|
||||
Name = categoryNameKey, // Store the key, not translated text
|
||||
Permission = permission,
|
||||
MenuFactories = new Dictionary<string, Func<CCSPlayerController, MenuBuilder>>(),
|
||||
ModuleLocalizer = moduleLocalizer // Store module's localizer
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu within a category (API for other plugins).
|
||||
/// </summary>
|
||||
@@ -66,6 +87,37 @@ public class MenuManager
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu with per-player localization support for modules.
|
||||
/// 🆕 NEW: Enables modules to provide localized menu names based on each player's css_lang!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuNameKey">Translation key from module's lang files.</param>
|
||||
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
/// <param name="commandName">Command name for permission override checking (optional).</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuNameKey, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission, string? commandName, Microsoft.Extensions.Localization.IStringLocalizer moduleLocalizer)
|
||||
{
|
||||
if (!_menuCategories.ContainsKey(categoryId))
|
||||
{
|
||||
RegisterCategory(categoryId, categoryId); // Auto-create category if it doesn't exist
|
||||
}
|
||||
|
||||
_menuCategories[categoryId].MenuFactories[menuId] = menuFactory;
|
||||
_menuCategories[categoryId].MenuNames[menuId] = menuNameKey; // Store the key
|
||||
_menuCategories[categoryId].MenuLocalizers[menuId] = moduleLocalizer; // Store localizer
|
||||
if (permission != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuPermissions[menuId] = permission;
|
||||
}
|
||||
if (commandName != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuCommandNames[menuId] = commandName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
/// </summary>
|
||||
@@ -88,7 +140,7 @@ public class MenuManager
|
||||
public MenuBuilder CreateMainMenu(CCSPlayerController player)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mainMenu = new MenuBuilder(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
var mainMenu = new MenuBuilder("sa_title", player, localizer);
|
||||
|
||||
foreach (var category in _menuCategories.Values)
|
||||
{
|
||||
@@ -98,8 +150,23 @@ public class MenuManager
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, category.Permission))
|
||||
continue;
|
||||
|
||||
// Get localized category name for this player
|
||||
// If category has a module localizer, use it; otherwise use main plugin localizer
|
||||
string localizedCategoryName;
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
if (category.ModuleLocalizer != null)
|
||||
{
|
||||
localizedCategoryName = category.ModuleLocalizer[category.Name] ?? category.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
localizedCategoryName = localizer?[category.Name] ?? category.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass player to CreateCategoryMenu
|
||||
mainMenu.AddSubMenu(category.Name, () => CreateCategoryMenu(category, player),
|
||||
mainMenu.AddSubMenu(localizedCategoryName, () => CreateCategoryMenu(category, player),
|
||||
permission: category.Permission);
|
||||
}
|
||||
|
||||
@@ -114,7 +181,24 @@ public class MenuManager
|
||||
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
||||
private MenuBuilder CreateCategoryMenu(MenuCategory category, CCSPlayerController player)
|
||||
{
|
||||
var categoryMenu = new MenuBuilder(category.Name);
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Get localized category name for this player
|
||||
// If category has a module localizer, use it; otherwise use main plugin localizer
|
||||
string localizedCategoryName;
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
if (category.ModuleLocalizer != null)
|
||||
{
|
||||
localizedCategoryName = category.ModuleLocalizer[category.Name] ?? category.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
localizedCategoryName = localizer?[category.Name] ?? category.Name;
|
||||
}
|
||||
}
|
||||
|
||||
var categoryMenu = new MenuBuilder(localizedCategoryName);
|
||||
|
||||
foreach (var kvp in category.MenuFactories)
|
||||
{
|
||||
@@ -159,8 +243,30 @@ public class MenuManager
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get localized menu name for this player
|
||||
// If menu has its own localizer, use it; otherwise use category or main plugin localizer
|
||||
string localizedMenuName;
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
if (category.MenuLocalizers.TryGetValue(menuId, out var menuLocalizer))
|
||||
{
|
||||
// Menu has its own module localizer
|
||||
localizedMenuName = menuLocalizer[menuName] ?? menuName;
|
||||
}
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
// Use category's module localizer
|
||||
localizedMenuName = category.ModuleLocalizer[menuName] ?? menuName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use main plugin localizer
|
||||
localizedMenuName = localizer?[menuName] ?? menuName;
|
||||
}
|
||||
}
|
||||
|
||||
// Call the actual factory with player parameter
|
||||
categoryMenu.AddSubMenu(menuName, () => menuFactory(player), permission: permission);
|
||||
categoryMenu.AddSubMenu(localizedMenuName, () => menuFactory(player), permission: permission);
|
||||
}
|
||||
|
||||
return categoryMenu.WithBackButton();
|
||||
@@ -190,12 +296,12 @@ public class MenuManager
|
||||
/// </summary>
|
||||
public void InitializeDefaultCategories()
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
RegisterCategory("players", localizer?["sa_menu_players_manage"] ?? "Manage Players", "@css/generic");
|
||||
RegisterCategory("server", localizer?["sa_menu_server_manage"] ?? "Server Management", "@css/generic");
|
||||
// RegisterCategory("fun", localizer?["sa_menu_fun_commands"] ?? "Fun Commands", "@css/generic");
|
||||
RegisterCategory("admin", localizer?["sa_menu_admins_manage"] ?? "Admin Management", "@css/root");
|
||||
// Register categories with translation keys instead of translated names
|
||||
// The actual translation will happen per-player in CreateMainMenu/CreateCategoryMenu
|
||||
RegisterCategory("players", "sa_menu_players_manage", "@css/generic");
|
||||
RegisterCategory("server", "sa_menu_server_manage", "@css/generic");
|
||||
// RegisterCategory("fun", "sa_menu_fun_commands", "@css/generic");
|
||||
RegisterCategory("admin", "sa_menu_admins_manage", "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -222,4 +328,17 @@ public class MenuCategory
|
||||
public Dictionary<string, string> MenuNames { get; set; } = [];
|
||||
public Dictionary<string, string> MenuPermissions { get; set; } = [];
|
||||
public Dictionary<string, string> MenuCommandNames { get; set; } = [];
|
||||
|
||||
// 🆕 NEW: Support for per-player localization in modules
|
||||
/// <summary>
|
||||
/// Optional IStringLocalizer from external module for per-player translation of category name.
|
||||
/// If null, Name is used as-is (for CS2-SimpleAdmin's built-in categories with translation keys).
|
||||
/// </summary>
|
||||
public Microsoft.Extensions.Localization.IStringLocalizer? ModuleLocalizer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores IStringLocalizer for each menu that uses module localization.
|
||||
/// Key: menuId, Value: module's localizer
|
||||
/// </summary>
|
||||
public Dictionary<string, Microsoft.Extensions.Localization.IStringLocalizer> MenuLocalizers { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.8-beta-3
|
||||
1.7.8-beta-4
|
||||
@@ -31,7 +31,7 @@
|
||||
"sa_team_spec": "Spec",
|
||||
|
||||
"sa_slap": "Slap",
|
||||
"sa_slay": "slay",
|
||||
"sa_slay": "Slay",
|
||||
"sa_kick": "Kick",
|
||||
"sa_ban": "Ban",
|
||||
"sa_gag": "Gag",
|
||||
|
||||
@@ -140,6 +140,16 @@ public interface ICS2_SimpleAdminApi
|
||||
/// </summary>
|
||||
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu category with per-player localization support for modules.
|
||||
/// 🆕 NEW: Supports per-player localization using module's IStringLocalizer!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category ID (unique identifier).</param>
|
||||
/// <param name="categoryNameKey">Translation key from module's lang files.</param>
|
||||
/// <param name="permission">Required permission to access this category.</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu in a category.
|
||||
/// </summary>
|
||||
@@ -163,6 +173,19 @@ public interface ICS2_SimpleAdminApi
|
||||
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu with per-player localization support for modules.
|
||||
/// 🆕 NEW: Supports per-player localization using module's IStringLocalizer!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuNameKey">Translation key from module's lang files.</param>
|
||||
/// <param name="menuFactory">Factory function that receives player and menu context.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
/// <param name="commandName">Command name for permission override checking (optional).</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuNameKey, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
/// </summary>
|
||||
|
||||
Binary file not shown.
@@ -145,20 +145,38 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
||||
// STEP 1: Register a menu category
|
||||
// This creates a new section in the main admin menu
|
||||
// Permission: @css/generic means all admins can see it
|
||||
//
|
||||
// ⚠️ LOCALIZATION OPTIONS:
|
||||
//
|
||||
// OPTION A - No translations (hard-coded text):
|
||||
_sharedApi.RegisterMenuCategory(
|
||||
"example", // Category ID (unique identifier)
|
||||
"Example Features", // Display name in admin menu
|
||||
"Example Features", // Display name (hard-coded, same for all players)
|
||||
"@css/generic" // Required permission
|
||||
);
|
||||
//
|
||||
// OPTION B - With per-player translations (🆕 NEW!):
|
||||
// If your module has lang/ folder with translations, use this pattern:
|
||||
// _sharedApi.RegisterMenuCategory(
|
||||
// "example", // Category ID
|
||||
// "example_category_name", // Translation key
|
||||
// "@css/generic", // Permission
|
||||
// Localizer! // Module's localizer
|
||||
// );
|
||||
// This will translate the category name per-player based on their css_lang setting!
|
||||
|
||||
// STEP 2: Register individual menu items in the category
|
||||
// 🆕 NEW: These use MenuContext API - factory receives (admin, context) parameters
|
||||
//
|
||||
// ⚠️ LOCALIZATION OPTIONS:
|
||||
//
|
||||
// OPTION A - No translations (hard-coded text):
|
||||
|
||||
// Example 1: Simple menu with options
|
||||
_sharedApi.RegisterMenu(
|
||||
"example", // Category ID
|
||||
"simple_action", // Menu ID (unique within category)
|
||||
"Simple Actions", // Display name
|
||||
"Simple Actions", // Display name (hard-coded)
|
||||
CreateSimpleActionMenu, // Factory method
|
||||
"@css/generic" // Required permission
|
||||
);
|
||||
@@ -167,7 +185,7 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"player_selection",
|
||||
"Select Player",
|
||||
"Select Player", // Display name
|
||||
CreatePlayerSelectionMenu,
|
||||
"@css/kick" // Requires kick permission
|
||||
);
|
||||
@@ -176,7 +194,7 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"nested_menu",
|
||||
"Give Credits",
|
||||
"Give Credits", // Display name
|
||||
CreateGiveCreditsMenu,
|
||||
"@css/generic"
|
||||
);
|
||||
@@ -185,12 +203,26 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"test_command",
|
||||
"Test Command",
|
||||
"Test Command", // Display name
|
||||
CreateTestCommandMenu,
|
||||
"@css/root", // Default permission
|
||||
"css_test" // Command name for override checking
|
||||
);
|
||||
|
||||
// OPTION B - With per-player translations (🆕 NEW!):
|
||||
// If your module has lang/ folder, use this pattern:
|
||||
// _sharedApi.RegisterMenu(
|
||||
// "example", // Category ID
|
||||
// "menu_id", // Menu ID
|
||||
// "menu_translation_key", // Translation key (NOT translated text!)
|
||||
// CreateYourMenu, // Factory method
|
||||
// "@css/generic", // Permission
|
||||
// "css_command", // Command name (optional)
|
||||
// Localizer! // Module's localizer
|
||||
// );
|
||||
// This will translate the menu name per-player based on their css_lang!
|
||||
// See FunCommands module for real example.
|
||||
|
||||
_menusRegistered = true;
|
||||
Logger.LogInformation("Example menus registered successfully!");
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -366,66 +366,73 @@ public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Con
|
||||
|
||||
try
|
||||
{
|
||||
_sharedApi.RegisterMenuCategory("fun", Localizer?["fun_category_name"] ?? "Fun Commands", "@css/generic");
|
||||
// 🆕 NEW: Per-player localization support for modules!
|
||||
// - This module has its own lang/ folder with translations
|
||||
// - We pass translation KEYS and the module's Localizer to the API
|
||||
// - SimpleAdmin will translate menu names per-player based on their css_lang
|
||||
// - Each player sees menus in their own language!
|
||||
_sharedApi.RegisterMenuCategory("fun", "fun_category_name", "@css/generic", Localizer!);
|
||||
|
||||
// Register menus with command names for permission override support
|
||||
// Server admins can override default permissions via CounterStrikeSharp admin system
|
||||
// Example: If "css_god" is overdden to "@css/vip", only VIPs will see the God Mode menu
|
||||
// Example: If "css_god" is overridden to "@css/vip", only VIPs will see the God Mode menu
|
||||
//
|
||||
// 🆕 NEW: All menus use translation keys and module's Localizer for per-player localization!
|
||||
|
||||
if (Config.GodCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "god",
|
||||
Localizer?["fun_menu_god"] ?? "God Mode",
|
||||
CreateGodModeMenu, "@css/cheats", "css_god");
|
||||
"fun_menu_god",
|
||||
CreateGodModeMenu, "@css/cheats", "css_god", Localizer!);
|
||||
|
||||
if (Config.NoclipCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "noclip",
|
||||
Localizer?["fun_menu_noclip"] ?? "No Clip",
|
||||
CreateNoClipMenu, "@css/cheats", "css_noclip");
|
||||
"fun_menu_noclip",
|
||||
CreateNoClipMenu, "@css/cheats", "css_noclip", Localizer!);
|
||||
|
||||
if (Config.RespawnCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "respawn",
|
||||
Localizer?["fun_menu_respawn"] ?? "Respawn",
|
||||
CreateRespawnMenu, "@css/cheats", "css_respawn");
|
||||
"fun_menu_respawn",
|
||||
CreateRespawnMenu, "@css/cheats", "css_respawn", Localizer!);
|
||||
|
||||
if (Config.GiveCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "give",
|
||||
Localizer?["fun_menu_give"] ?? "Give Weapon",
|
||||
CreateGiveWeaponMenu, "@css/cheats", "css_give");
|
||||
"fun_menu_give",
|
||||
CreateGiveWeaponMenu, "@css/cheats", "css_give", Localizer!);
|
||||
|
||||
if (Config.StripCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "strip",
|
||||
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
|
||||
CreateStripWeaponsMenu, "@css/slay", "css_strip");
|
||||
"fun_menu_strip",
|
||||
CreateStripWeaponsMenu, "@css/slay", "css_strip", Localizer!);
|
||||
|
||||
if (Config.FreezeCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "freeze",
|
||||
Localizer?["fun_menu_freeze"] ?? "Freeze",
|
||||
CreateFreezeMenu, "@css/slay", "css_freeze");
|
||||
"fun_menu_freeze",
|
||||
CreateFreezeMenu, "@css/slay", "css_freeze", Localizer!);
|
||||
|
||||
if (Config.HpCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "hp",
|
||||
Localizer?["fun_menu_hp"] ?? "Set HP",
|
||||
CreateSetHpMenu, "@css/slay", "css_hp");
|
||||
"fun_menu_hp",
|
||||
CreateSetHpMenu, "@css/slay", "css_hp", Localizer!);
|
||||
|
||||
if (Config.SpeedCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "speed",
|
||||
Localizer?["fun_menu_speed"] ?? "Set Speed",
|
||||
CreateSetSpeedMenu, "@css/slay", "css_speed");
|
||||
"fun_menu_speed",
|
||||
CreateSetSpeedMenu, "@css/slay", "css_speed", Localizer!);
|
||||
|
||||
if (Config.GravityCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "gravity",
|
||||
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
|
||||
CreateSetGravityMenu, "@css/slay", "css_gravity");
|
||||
"fun_menu_gravity",
|
||||
CreateSetGravityMenu, "@css/slay", "css_gravity", Localizer!);
|
||||
|
||||
if (Config.MoneyCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "money",
|
||||
Localizer?["fun_menu_money"] ?? "Set Money",
|
||||
CreateSetMoneyMenu, "@css/slay", "css_money");
|
||||
"fun_menu_money",
|
||||
CreateSetMoneyMenu, "@css/slay", "css_money", Localizer!);
|
||||
|
||||
if (Config.ResizeCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "resize",
|
||||
Localizer?["fun_menu_resize"] ?? "Resize Player",
|
||||
CreateSetResizeMenu, "@css/slay", "css_resize");
|
||||
"fun_menu_resize",
|
||||
CreateSetResizeMenu, "@css/slay", "css_resize", Localizer!);
|
||||
|
||||
_menusRegistered = true;
|
||||
Logger.LogInformation("Fun menus registered successfully!");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Globalization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
|
||||
namespace CS2_SimpleAdmin_FunCommands;
|
||||
|
||||
@@ -139,8 +140,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// </summary>
|
||||
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var weaponMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
@@ -212,8 +220,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// </summary>
|
||||
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var hpSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
@@ -222,8 +237,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
// Translate option label per-player
|
||||
string optionLabel;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
optionLabel = Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP";
|
||||
}
|
||||
|
||||
_sharedApi.AddMenuOption(hpSelectionMenu,
|
||||
Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP",
|
||||
optionLabel,
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
@@ -261,8 +283,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// </summary>
|
||||
private object CreateSpeedSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var speedSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
@@ -275,8 +304,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
foreach (var (speed, display) in speedValues)
|
||||
{
|
||||
// Translate option label per-player
|
||||
string optionLabel;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
optionLabel = Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}";
|
||||
}
|
||||
|
||||
_sharedApi.AddMenuOption(speedSelectionMenu,
|
||||
Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}",
|
||||
optionLabel,
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
@@ -316,8 +352,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
private object CreateGravitySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var gravitySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
var gravityValues = new[]
|
||||
@@ -365,8 +408,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
private object CreateMoneySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var moneySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
var moneyValues = new[] { 0, 1000, 2500, 5000, 10000, 16000 };
|
||||
@@ -407,8 +457,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
private object CreateResizeSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var resizeSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
var resizeValues = new[]
|
||||
|
||||
@@ -75,10 +75,17 @@ public class MyModule : BasePlugin
|
||||
if (_api == null) return;
|
||||
|
||||
// 1. Register a new category
|
||||
// IMPORTANT: If your module has lang/ folder with translations, use YOUR module's Localizer!
|
||||
// Example: _api.RegisterMenuCategory("mymodule", Localizer?["mymodule_category_name"] ?? "My Module", "@css/generic");
|
||||
//
|
||||
// This will use YOUR module's translations in server language.
|
||||
// For modules without translations, you can use hard-coded text:
|
||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||
|
||||
// 2. Register menu items in the category
|
||||
// 🆕 NEW: Use MenuContext-aware overload (no duplication!)
|
||||
// NOTE: Use YOUR module's Localizer if you have lang/ folder with translations:
|
||||
// _api.RegisterMenu("mymodule", "action1", Localizer?["mymodule_menu_action1"] ?? "Action 1", CreateAction1Menu, "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "action1", "Action 1", CreateAction1Menu, "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "action2", "Action 2", CreateAction2Menu, "@css/kick");
|
||||
}
|
||||
@@ -356,12 +363,27 @@ Registers a new menu category that appears in the main admin menu.
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Unique identifier for the category (e.g., "fun", "vip", "economy")
|
||||
- `categoryName` - Display name shown in menu (e.g., "Fun Commands")
|
||||
- `categoryName` - **TRANSLATION KEY** for the display name (e.g., "vip_category_name", NOT "VIP Features")
|
||||
- `permission` - Required permission to see the category (default: "@css/generic")
|
||||
|
||||
**IMPORTANT FOR MODULES WITH TRANSLATIONS:**
|
||||
- If your module has a `lang/` folder with translation files, use **YOUR module's Localizer**
|
||||
- This will display menu names in the **server's language** (not per-player)
|
||||
- For per-player localization, only **CS2-SimpleAdmin's built-in menus** support this currently
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// ✅ CORRECT: Module with translations uses its own Localizer
|
||||
_api.RegisterMenuCategory("vip", Localizer?["vip_category_name"] ?? "VIP Features", "@css/vip");
|
||||
// In YOUR module's lang/en.json: "vip_category_name": "VIP Features"
|
||||
// In YOUR module's lang/pl.json: "vip_category_name": "Funkcje VIP"
|
||||
|
||||
// ✅ ALSO CORRECT: Module without translations uses hard-coded text
|
||||
_api.RegisterMenuCategory("vip", "VIP Features", "@css/vip");
|
||||
|
||||
// ❌ INCORRECT: Using translation key without Localizer
|
||||
_api.RegisterMenuCategory("vip", "vip_category_name", "@css/vip");
|
||||
// This would display "vip_category_name" literally!
|
||||
```
|
||||
|
||||
### 2. Menu Registration
|
||||
@@ -373,13 +395,28 @@ Registers a menu item within a category.
|
||||
**Parameters:**
|
||||
- `categoryId` - The category to add this menu to
|
||||
- `menuId` - Unique identifier for the menu
|
||||
- `menuName` - Display name in the category menu
|
||||
- `menuFactory` - Function that creates the menu when selected (receives admin player)
|
||||
- `menuName` - Display name for the menu (use **your module's Localizer** if you have translations)
|
||||
- `menuFactory` - Function that creates the menu when selected (receives admin player and MenuContext)
|
||||
- `permission` - Optional permission required to see this menu item
|
||||
|
||||
**IMPORTANT FOR MODULES WITH TRANSLATIONS:**
|
||||
- Use **your module's Localizer** if you have a `lang/` folder: `Localizer?["key"] ?? "Fallback"`
|
||||
- This shows menu in **server language**, not per-player
|
||||
- Per-player localization is only available for CS2-SimpleAdmin's built-in menus
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// ✅ CORRECT: Module with translations uses its own Localizer
|
||||
_api.RegisterMenu("fun", "godmode", Localizer?["fun_menu_god"] ?? "God Mode", CreateGodModeMenu, "@css/cheats");
|
||||
// In YOUR module's lang/en.json: "fun_menu_god": "God Mode"
|
||||
// In YOUR module's lang/pl.json: "fun_menu_god": "Tryb Boga"
|
||||
|
||||
// ✅ ALSO CORRECT: Module without translations uses hard-coded text
|
||||
_api.RegisterMenu("fun", "godmode", "God Mode", CreateGodModeMenu, "@css/cheats");
|
||||
|
||||
// ❌ INCORRECT: Using translation key without Localizer
|
||||
_api.RegisterMenu("fun", "godmode", "fun_menu_god", CreateGodModeMenu, "@css/cheats");
|
||||
// This would display "fun_menu_god" literally!
|
||||
```
|
||||
|
||||
#### `UnregisterMenu(string categoryId, string menuId)`
|
||||
@@ -625,12 +662,32 @@ private object CreateAdminToolsMenu(CCSPlayerController admin)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always check for API availability**
|
||||
1. **Use your module's Localizer for translations** 🌍
|
||||
```csharp
|
||||
// ✅ CORRECT: Module with translations uses its own Localizer
|
||||
_api.RegisterMenuCategory("mymodule", Localizer?["mymodule_category"] ?? "My Module", "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "action", Localizer?["mymodule_menu_action"] ?? "My Action", CreateMenu, "@css/generic");
|
||||
|
||||
// ✅ ALSO CORRECT: Module without translations uses hard-coded text
|
||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "action", "My Action", CreateMenu, "@css/generic");
|
||||
|
||||
// ❌ WRONG: Using translation key without Localizer
|
||||
_api.RegisterMenuCategory("mymodule", "mymodule_category", "@css/generic");
|
||||
// This would display "mymodule_category" literally!
|
||||
```
|
||||
|
||||
**✅ Per-player localization now available!**
|
||||
- Both CS2-SimpleAdmin built-in menus AND module menus support per-player localization
|
||||
- Each player sees menus in their own language based on their `css_lang` setting
|
||||
- See "Advanced: Per-Player Localization for Modules" section below for implementation details
|
||||
|
||||
2. **Always check for API availability**
|
||||
```csharp
|
||||
if (_api == null) return;
|
||||
```
|
||||
|
||||
2. **Validate player state before actions**
|
||||
3. **Validate player state before actions**
|
||||
```csharp
|
||||
if (target.IsValid && target.PlayerPawn?.Value != null)
|
||||
{
|
||||
@@ -638,11 +695,11 @@ private object CreateAdminToolsMenu(CCSPlayerController admin)
|
||||
}
|
||||
```
|
||||
|
||||
3. **Use descriptive category and menu IDs**
|
||||
4. **Use descriptive category and menu IDs**
|
||||
- Good: `"economy"`, `"vip_features"`, `"fun_commands"`
|
||||
- Bad: `"cat1"`, `"menu"`, `"test"`
|
||||
|
||||
4. **Clean up on unload**
|
||||
5. **Clean up on unload**
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
@@ -651,14 +708,14 @@ private object CreateAdminToolsMenu(CCSPlayerController admin)
|
||||
}
|
||||
```
|
||||
|
||||
5. **Use appropriate permissions**
|
||||
6. **Use appropriate permissions**
|
||||
- `@css/generic` - All admins
|
||||
- `@css/ban` - Admins who can ban
|
||||
- `@css/kick` - Admins who can kick
|
||||
- `@css/root` - Root admins only
|
||||
- Custom permissions from your module
|
||||
|
||||
6. **Handle hot reload**
|
||||
7. **Handle hot reload**
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Fallback for hot reload case
|
||||
@@ -717,3 +774,72 @@ See the `CS2-SimpleAdmin_FunCommands` module in the `Modules/` directory for a c
|
||||
**Q: API is null in OnAllPluginsLoaded**
|
||||
- Wait for the `OnSimpleAdminReady` event instead of immediate registration
|
||||
- Make sure CS2-SimpleAdmin is loaded before your module
|
||||
|
||||
## Advanced: Per-Player Localization for Modules (✅ NOW AVAILABLE!)
|
||||
|
||||
**🆕 NEW:** Module menus now support **per-player localization** based on `css_lang`!
|
||||
|
||||
Both CS2-SimpleAdmin's built-in menus AND module menus can show in each player's configured language.
|
||||
|
||||
### How to Use Per-Player Localization in Your Module
|
||||
|
||||
**1. Register Category with Localizer:**
|
||||
|
||||
```csharp
|
||||
// Pass translation KEY (not translated text) and module's Localizer
|
||||
_api.RegisterMenuCategory(
|
||||
"mymodule",
|
||||
"mymodule_category_name", // Translation key from your lang/ folder
|
||||
"@css/generic",
|
||||
Localizer! // Your module's localizer
|
||||
);
|
||||
```
|
||||
|
||||
**2. Register Menus with Localizer:**
|
||||
|
||||
```csharp
|
||||
_api.RegisterMenu(
|
||||
"mymodule",
|
||||
"action",
|
||||
"mymodule_menu_action", // Translation key from your lang/ folder
|
||||
CreateMenu,
|
||||
"@css/generic", // Permission
|
||||
"css_mycommand", // Command name for override (optional)
|
||||
Localizer! // Your module's localizer
|
||||
);
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
1. Your module passes its `IStringLocalizer` and translation **key** (not translated text)
|
||||
2. SimpleAdmin's `MenuManager` stores both the key and the localizer
|
||||
3. When displaying menu to a player, MenuManager translates using:
|
||||
```csharp
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
localizedName = moduleLocalizer[translationKey];
|
||||
}
|
||||
```
|
||||
4. Each player sees menus in their own language based on their `css_lang` setting!
|
||||
|
||||
**Complete Example:**
|
||||
|
||||
See `Modules/CS2-SimpleAdmin_FunCommands/` for a real implementation:
|
||||
|
||||
```csharp
|
||||
// Register category with per-player localization
|
||||
_api.RegisterMenuCategory("fun", "fun_category_name", "@css/generic", Localizer!);
|
||||
|
||||
// Register menu with per-player localization
|
||||
_api.RegisterMenu("fun", "god", "fun_menu_god",
|
||||
CreateGodModeMenu, "@css/cheats", "css_god", Localizer!);
|
||||
```
|
||||
|
||||
**Without Per-Player Localization (backwards compatible):**
|
||||
|
||||
If you don't need per-player localization, the old API still works:
|
||||
|
||||
```csharp
|
||||
// Hard-coded text (same for all players)
|
||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "action", "Do Action", CreateMenu, "@css/generic");
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user