#293 Translatable help messages: basic structure

(work in progress)
- Create service that provides localized messages when available for HelpProvider
This commit is contained in:
ljacqu 2016-10-01 14:40:08 +02:00
parent 113a3f346c
commit f6a2b2b34b
7 changed files with 256 additions and 57 deletions

View File

@ -9,7 +9,7 @@ import java.util.List;
/** /**
* Helper class for displaying the syntax of a command properly to a user. * Helper class for displaying the syntax of a command properly to a user.
*/ */
class CommandSyntaxHelper { final class CommandSyntaxHelper {
private CommandSyntaxHelper() { private CommandSyntaxHelper() {
} }

View File

@ -0,0 +1,49 @@
package fr.xephi.authme.command.help;
/**
* Common, non-generic keys for messages used when showing command help.
* All keys are prefixed with {@code common}.
*/
public enum HelpMessageKey {
SHORT_DESCRIPTION("description.short", "Short description"),
DETAILED_DESCRIPTION("description.detailed", "Detailed description"),
USAGE("usage", "Usage"),
ARGUMENTS("arguments", "Arguments"),
OPTIONAL("optional", "(Optional)"),
HAS_PERMISSION("hasPermission", "You have permission"),
NO_PERMISSION("noPermission", "No permission"),
ALTERNATIVES("alternatives", "Alternatives"),
DEFAULT("default", "Default"),
RESULT("result", "Result"),
PERMISSIONS("permissions", "Permissions"),
COMMANDS("commands", "Commands");
private final String key;
private final String fallback;
HelpMessageKey(String key, String fallback) {
this.key = "common." + key;
this.fallback = fallback;
}
public String getKey() {
return key;
}
public String getFallback() {
return fallback;
}
}

View File

@ -0,0 +1,113 @@
package fr.xephi.authme.command.help;
import com.google.common.base.CaseFormat;
import fr.xephi.authme.command.CommandArgumentDescription;
import fr.xephi.authme.command.CommandDescription;
import fr.xephi.authme.command.CommandUtils;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.permission.DefaultPermission;
import fr.xephi.authme.util.FileUtils;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import javax.inject.Inject;
import java.io.File;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Manages translatable help messages.
*/
public class HelpMessagesService implements Reloadable {
private static final String COMMAND_PREFIX = "command.";
private static final String DESCRIPTION_SUFFIX = ".description";
private static final String DETAILED_DESCRIPTION_SUFFIX = ".detailedDescription";
private static final String DEFAULT_PERMISSIONS_PATH = "common.defaultPermissions.";
private final File dataFolder;
// FIXME: Make configurable
private String file = "messages/help_en.yml";
private FileConfiguration fileConfiguration;
@Inject
HelpMessagesService(@DataFolder File dataFolder) {
File messagesFile = new File(dataFolder, "messages/help_en.yml");
if (!FileUtils.copyFileFromResource(messagesFile, file)) {
throw new IllegalStateException("Could not copy help message");
}
this.dataFolder = dataFolder;
fileConfiguration = YamlConfiguration.loadConfiguration(messagesFile);
}
/**
* Creates a copy of the supplied command description with localized messages where present.
*
* @param command the command to build a localized version of
* @return the localized description
*/
public CommandDescription buildLocalizedDescription(CommandDescription command) {
final String path = getCommandPath(command);
if (fileConfiguration.get(path) == null) {
// Messages file does not have a section for this command - return the provided command
return command;
}
CommandDescription.CommandBuilder builder = CommandDescription.builder()
.description(getText(path + DESCRIPTION_SUFFIX, command::getDescription))
.detailedDescription(getText(path + DETAILED_DESCRIPTION_SUFFIX, command::getDetailedDescription))
.executableCommand(command.getExecutableCommand())
.parent(command.getParent())
.labels(command.getLabels())
.permission(command.getPermission());
int i = 1;
for (CommandArgumentDescription argument : command.getArguments()) {
String argPath = path + ".arg" + i;
String label = getText(argPath + ".label", argument::getName);
String description = getText(argPath + ".description", argument::getDescription);
builder.withArgument(label, description, argument.isOptional());
++i;
}
return builder.build();
}
public String getMessage(HelpMessageKey key) {
String message = fileConfiguration.getString(key.getKey());
return message == null
? key.getFallback()
: message;
}
public String getMessage(DefaultPermission defaultPermission) {
// e.g. {default_permissions_path}.opOnly for DefaultPermission.OP_ONLY
String path = DEFAULT_PERMISSIONS_PATH +
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name());
String message = fileConfiguration.getString(path);
if (message != null) {
return message;
}
return defaultPermission.name(); // FIXME: Default message
}
@Override
public void reload() {
fileConfiguration = YamlConfiguration.loadConfiguration(new File(dataFolder, "messages/help_en.yml"));
}
private String getText(String path, Supplier<String> defaultTextGetter) {
String message = fileConfiguration.getString(path);
return message == null
? defaultTextGetter.get()
: message;
}
private static String getCommandPath(CommandDescription command) {
return COMMAND_PREFIX + CommandUtils.constructParentList(command)
.stream()
.map(cmd -> cmd.getLabels().get(0))
.collect(Collectors.joining("."));
}
}

View File

@ -19,6 +19,8 @@ import javax.inject.Inject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static fr.xephi.authme.command.help.HelpMessageKey.DETAILED_DESCRIPTION;
import static fr.xephi.authme.command.help.HelpMessageKey.SHORT_DESCRIPTION;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@ -45,11 +47,13 @@ public class HelpProvider implements SettingsDependent {
public static final int ALL_OPTIONS = ~HIDE_COMMAND; public static final int ALL_OPTIONS = ~HIDE_COMMAND;
private final PermissionsManager permissionsManager; private final PermissionsManager permissionsManager;
private final HelpMessagesService helpMessagesService;
private String helpHeader; private String helpHeader;
@Inject @Inject
HelpProvider(PermissionsManager permissionsManager, Settings settings) { HelpProvider(PermissionsManager permissionsManager, HelpMessagesService helpMessagesService, Settings settings) {
this.permissionsManager = permissionsManager; this.permissionsManager = permissionsManager;
this.helpMessagesService = helpMessagesService;
reload(settings); reload(settings);
} }
@ -61,7 +65,7 @@ public class HelpProvider implements SettingsDependent {
List<String> lines = new ArrayList<>(); List<String> lines = new ArrayList<>();
lines.add(ChatColor.GOLD + "==========[ " + helpHeader + " HELP ]=========="); lines.add(ChatColor.GOLD + "==========[ " + helpHeader + " HELP ]==========");
CommandDescription command = result.getCommandDescription(); CommandDescription command = helpMessagesService.buildLocalizedDescription(result.getCommandDescription());
List<String> labels = ImmutableList.copyOf(result.getLabels()); List<String> labels = ImmutableList.copyOf(result.getLabels());
List<String> correctLabels = ImmutableList.copyOf(filterCorrectLabels(command, labels)); List<String> correctLabels = ImmutableList.copyOf(filterCorrectLabels(command, labels));
@ -75,7 +79,7 @@ public class HelpProvider implements SettingsDependent {
printArguments(command, lines); printArguments(command, lines);
} }
if (hasFlag(SHOW_PERMISSIONS, options) && sender != null) { if (hasFlag(SHOW_PERMISSIONS, options) && sender != null) {
printPermissions(command, sender, permissionsManager, lines); printPermissions(command, sender, lines);
} }
if (hasFlag(SHOW_ALTERNATIVES, options)) { if (hasFlag(SHOW_ALTERNATIVES, options)) {
printAlternatives(command, correctLabels, lines); printAlternatives(command, correctLabels, lines);
@ -106,37 +110,40 @@ public class HelpProvider implements SettingsDependent {
helpHeader = settings.getProperty(PluginSettings.HELP_HEADER); helpHeader = settings.getProperty(PluginSettings.HELP_HEADER);
} }
private static void printDetailedDescription(CommandDescription command, List<String> lines) { private void printDetailedDescription(CommandDescription command, List<String> lines) {
lines.add(ChatColor.GOLD + "Short description: " + ChatColor.WHITE + command.getDescription()); lines.add(ChatColor.GOLD + helpMessagesService.getMessage(SHORT_DESCRIPTION) + ": "
lines.add(ChatColor.GOLD + "Detailed description:"); + ChatColor.WHITE + command.getDescription());
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(DETAILED_DESCRIPTION) + ":");
lines.add(ChatColor.WHITE + " " + command.getDetailedDescription()); lines.add(ChatColor.WHITE + " " + command.getDetailedDescription());
} }
private static void printArguments(CommandDescription command, List<String> lines) { private void printArguments(CommandDescription command, List<String> lines) {
if (command.getArguments().isEmpty()) { if (command.getArguments().isEmpty()) {
return; return;
} }
lines.add(ChatColor.GOLD + "Arguments:"); lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.ARGUMENTS) + ":");
StringBuilder argString = new StringBuilder(); StringBuilder argString = new StringBuilder();
String optionalText = " (" + helpMessagesService.getMessage(HelpMessageKey.OPTIONAL) + ")";
for (CommandArgumentDescription argument : command.getArguments()) { for (CommandArgumentDescription argument : command.getArguments()) {
argString.setLength(0); argString.setLength(0);
argString.append(" ").append(ChatColor.YELLOW).append(ChatColor.ITALIC).append(argument.getName()) argString.append(" ").append(ChatColor.YELLOW).append(ChatColor.ITALIC).append(argument.getName())
.append(": ").append(ChatColor.WHITE).append(argument.getDescription()); .append(": ").append(ChatColor.WHITE).append(argument.getDescription());
if (argument.isOptional()) { if (argument.isOptional()) {
argString.append(ChatColor.GRAY).append(ChatColor.ITALIC).append(" (Optional)"); argString.append(ChatColor.GRAY).append(ChatColor.ITALIC).append(optionalText);
} }
lines.add(argString.toString()); lines.add(argString.toString());
} }
} }
private static void printAlternatives(CommandDescription command, List<String> correctLabels, List<String> lines) { private void printAlternatives(CommandDescription command, List<String> correctLabels, List<String> lines) {
if (command.getLabels().size() <= 1 || correctLabels.size() <= 1) { if (command.getLabels().size() <= 1 || correctLabels.size() <= 1) {
return; return;
} }
lines.add(ChatColor.GOLD + "Alternatives:"); lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.ALTERNATIVES) + ":");
// Get the label used // Get the label used
final String parentLabel = correctLabels.get(0); final String parentLabel = correctLabels.get(0);
final String childLabel = correctLabels.get(1); final String childLabel = correctLabels.get(1);
@ -149,44 +156,53 @@ public class HelpProvider implements SettingsDependent {
} }
} }
private static void printPermissions(CommandDescription command, CommandSender sender, private void printPermissions(CommandDescription command, CommandSender sender, List<String> lines) {
PermissionsManager permissionsManager, List<String> lines) {
PermissionNode permission = command.getPermission(); PermissionNode permission = command.getPermission();
if (permission == null) { if (permission == null) {
return; return;
} }
lines.add(ChatColor.GOLD + "Permissions:"); lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.PERMISSIONS) + ":");
boolean hasPermission = permissionsManager.hasPermission(sender, permission); boolean hasPermission = permissionsManager.hasPermission(sender, permission);
final String nodePermsString = "" + ChatColor.GRAY + ChatColor.ITALIC lines.add(String.format(" " + ChatColor.YELLOW + ChatColor.ITALIC + "%s" + ChatColor.GRAY + " (%s)",
+ (hasPermission ? " (You have permission)" : " (No permission)"); permission.getNode(), getLocalPermissionText(hasPermission)));
lines.add(" " + ChatColor.YELLOW + ChatColor.ITALIC + permission.getNode() + nodePermsString);
// Addendum to the line to specify whether the sender has permission or not when default is OP_ONLY // Addendum to the line to specify whether the sender has permission or not when default is OP_ONLY
final DefaultPermission defaultPermission = permission.getDefaultPermission(); final DefaultPermission defaultPermission = permission.getDefaultPermission();
String addendum = ""; String addendum = "";
if (DefaultPermission.OP_ONLY.equals(defaultPermission)) { if (DefaultPermission.OP_ONLY.equals(defaultPermission)) {
addendum = defaultPermission.evaluate(sender) addendum = " (" + getLocalPermissionText(defaultPermission.evaluate(sender)) + ")";
? " (You have permission)"
: " (No permission)";
} }
lines.add(ChatColor.GOLD + "Default: " + ChatColor.GRAY + ChatColor.ITALIC lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.DEFAULT) + ": "
+ defaultPermission.getTitle() + addendum); + ChatColor.GRAY + ChatColor.ITALIC + helpMessagesService.getMessage(defaultPermission) + addendum);
// Evaluate if the sender has permission to the command // Evaluate if the sender has permission to the command
ChatColor permissionColor;
String permissionText;
if (permissionsManager.hasPermission(sender, command.getPermission())) { if (permissionsManager.hasPermission(sender, command.getPermission())) {
lines.add(ChatColor.GOLD + " Result: " + ChatColor.GREEN + ChatColor.ITALIC + "You have permission"); permissionColor = ChatColor.GREEN;
permissionText = getLocalPermissionText(true);
} else { } else {
lines.add(ChatColor.GOLD + " Result: " + ChatColor.DARK_RED + ChatColor.ITALIC + "No permission"); permissionColor = ChatColor.DARK_RED;
permissionText = getLocalPermissionText(false);
} }
lines.add(String.format(ChatColor.GOLD + " %s: %s" + ChatColor.ITALIC + "%s",
helpMessagesService.getMessage(HelpMessageKey.RESULT), permissionColor, permissionText));
} }
private static void printChildren(CommandDescription command, List<String> parentLabels, List<String> lines) { private String getLocalPermissionText(boolean hasPermission) {
if (hasPermission) {
return helpMessagesService.getMessage(HelpMessageKey.HAS_PERMISSION);
}
return helpMessagesService.getMessage(HelpMessageKey.NO_PERMISSION);
}
private void printChildren(CommandDescription command, List<String> parentLabels, List<String> lines) {
if (command.getChildren().isEmpty()) { if (command.getChildren().isEmpty()) {
return; return;
} }
lines.add(ChatColor.GOLD + "Commands:"); lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.COMMANDS) + ":");
String parentCommandPath = CommandUtils.labelsToString(parentLabels); String parentCommandPath = CommandUtils.labelsToString(parentLabels);
for (CommandDescription child : command.getChildren()) { for (CommandDescription child : command.getChildren()) {
lines.add(" /" + parentCommandPath + " " + child.getLabels().get(0) lines.add(" /" + parentCommandPath + " " + child.getLabels().get(0)

View File

@ -8,7 +8,7 @@ import org.bukkit.permissions.ServerOperator;
public enum DefaultPermission { public enum DefaultPermission {
/** No one has permission. */ /** No one has permission. */
NOT_ALLOWED("No permission") { NOT_ALLOWED {
@Override @Override
public boolean evaluate(ServerOperator sender) { public boolean evaluate(ServerOperator sender) {
return false; return false;
@ -16,7 +16,7 @@ public enum DefaultPermission {
}, },
/** Only players with OP status have permission. */ /** Only players with OP status have permission. */
OP_ONLY("OP's only") { OP_ONLY {
@Override @Override
public boolean evaluate(ServerOperator sender) { public boolean evaluate(ServerOperator sender) {
return sender != null && sender.isOp(); return sender != null && sender.isOp();
@ -24,24 +24,13 @@ public enum DefaultPermission {
}, },
/** Everyone is granted permission. */ /** Everyone is granted permission. */
ALLOWED("Everyone allowed") { ALLOWED {
@Override @Override
public boolean evaluate(ServerOperator sender) { public boolean evaluate(ServerOperator sender) {
return true; return true;
} }
}; };
/** Textual representation of the default permission. */
private final String title;
/**
* Constructor.
* @param title The textual representation
*/
DefaultPermission(String title) {
this.title = title;
}
/** /**
* Evaluates whether permission is granted to the sender or not. * Evaluates whether permission is granted to the sender or not.
* *
@ -50,13 +39,4 @@ public enum DefaultPermission {
*/ */
public abstract boolean evaluate(ServerOperator sender); public abstract boolean evaluate(ServerOperator sender);
/**
* Return the textual representation.
*
* @return the textual representation
*/
public String getTitle() {
return title;
}
} }

View File

@ -0,0 +1,30 @@
common:
description.short: 'Short description'
description.detailed: 'Detailed description'
usage: 'Usage'
arguments: 'Arguments'
permissions: 'Permissions'
optional: '(Optional)'
hasPermission: 'You have permission'
noPermission: 'No permission'
default: 'Default'
alternatives: 'Alternatives'
result: 'Result'
commands: 'Commands'
defaultPermissions:
notAllowed: 'No permission'
opOnly: 'OP''s only'
allowed: 'Everyone allowed'
command:
register:
description: 'Register with this command'
detailedDescription: 'Use /register <pw> <pw>'
authme.register:
description: 'Admin registration'
detailedDescription: 'Used by l33t adminz'
arg1:
label: 'n4me'
description: 'The name to save'
arg2:
label: 'passw0rd'
description: 'Password to use yo'

View File

@ -1,5 +1,8 @@
package fr.xephi.authme.command.help; package fr.xephi.authme.command.help;
import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandDescription;
import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.FoundCommandResult;
import fr.xephi.authme.command.FoundResultStatus; import fr.xephi.authme.command.FoundResultStatus;
@ -10,10 +13,12 @@ import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.PluginSettings;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -41,12 +46,21 @@ import static org.mockito.Mockito.verify;
/** /**
* Test for {@link HelpProvider}. * Test for {@link HelpProvider}.
*/ */
@RunWith(DelayedInjectionRunner.class)
public class HelpProviderTest { public class HelpProviderTest {
private static final String HELP_HEADER = "Help"; private static final String HELP_HEADER = "Help";
private static Set<CommandDescription> commands; private static Set<CommandDescription> commands;
@InjectDelayed
private HelpProvider helpProvider; private HelpProvider helpProvider;
@Mock
private PermissionsManager permissionsManager; private PermissionsManager permissionsManager;
@Mock
private HelpMessagesService helpMessagesService;
@Mock
private Settings settings;
@Mock
private CommandSender sender; private CommandSender sender;
@BeforeClass @BeforeClass
@ -54,16 +68,13 @@ public class HelpProviderTest {
commands = TestCommandsUtil.generateCommands(); commands = TestCommandsUtil.generateCommands();
} }
@Before @BeforeInjecting
public void setUpHelpProvider() { public void setInitialSettings() {
permissionsManager = mock(PermissionsManager.class);
Settings settings = mock(Settings.class);
given(settings.getProperty(PluginSettings.HELP_HEADER)).willReturn(HELP_HEADER); given(settings.getProperty(PluginSettings.HELP_HEADER)).willReturn(HELP_HEADER);
helpProvider = new HelpProvider(permissionsManager, settings);
sender = mock(CommandSender.class);
} }
@Test @Test
@Ignore // FIXME: Fix test
public void shouldShowLongDescription() { public void shouldShowLongDescription() {
// given // given
CommandDescription command = getCommandWithLabel(commands, "authme", "login"); CommandDescription command = getCommandWithLabel(commands, "authme", "login");