Allow for more easily using translatable components with MinecraftHelp (#197)

*  Allow for more easily using translatable components with MinecraftHelp

* Add missing Javadoc
This commit is contained in:
Jason 2021-01-03 22:19:48 -08:00 committed by Alexander Söderberg
parent 8913b2495e
commit c8fdf22f4b
3 changed files with 144 additions and 47 deletions

View file

@ -28,6 +28,8 @@ import cloud.commandframework.captions.CaptionVariable;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays;
public class ParserException extends IllegalArgumentException { public class ParserException extends IllegalArgumentException {
private static final long serialVersionUID = -4409795575435072170L; private static final long serialVersionUID = -4409795575435072170L;
@ -56,6 +58,27 @@ public class ParserException extends IllegalArgumentException {
); );
} }
/**
* Get the error caption for this parser exception
*
* @return The caption
* @since 1.4.0
*/
public @NonNull Caption errorCaption() {
return this.errorCaption;
}
/**
* Get a copy of the caption variables present in this parser exception.
* The returned array may be empty if no variables are present.
*
* @return The caption variables
* @since 1.4.0
*/
public @NonNull CaptionVariable @NonNull [] captionVariables() {
return Arrays.copyOf(this.captionVariables, this.captionVariables.length);
}
/** /**
* Get the argument parser * Get the argument parser
* *

View file

@ -26,8 +26,10 @@ package cloud.commandframework.minecraft.extras;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.translation.GlobalTranslator;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
final class ComponentHelper { final class ComponentHelper {
@ -63,7 +65,8 @@ final class ComponentHelper {
if (component instanceof TextComponent) { if (component instanceof TextComponent) {
length += ((TextComponent) component).content().length(); length += ((TextComponent) component).content().length();
} }
for (final Component child : component.children()) { final Component translated = GlobalTranslator.render(component, Locale.getDefault());
for (final Component child : translated.children()) {
length += length(child); length += length(child);
} }
return length; return length;

View file

@ -44,6 +44,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import static net.kyori.adventure.text.Component.space; import static net.kyori.adventure.text.Component.space;
@ -92,7 +93,10 @@ public final class MinecraftHelp<C> {
private final Map<String, String> messageMap = new HashMap<>(); private final Map<String, String> messageMap = new HashMap<>();
private Predicate<Command<C>> commandFilter = c -> true; private Predicate<Command<C>> commandFilter = c -> true;
private BiFunction<C, String, String> messageProvider = (sender, key) -> this.messageMap.get(key); private BiFunction<C, String, String> stringMessageProvider = (sender, key) -> this.messageMap.get(key);
private MessageProvider<C> messageProvider =
(sender, key, args) -> text(this.stringMessageProvider.apply(sender, key));
private Function<String, Component> descriptionDecorator = Component::text;
private HelpColors colors = DEFAULT_HELP_COLORS; private HelpColors colors = DEFAULT_HELP_COLORS;
private int headerFooterLength = DEFAULT_HEADER_FOOTER_LENGTH; private int headerFooterLength = DEFAULT_HEADER_FOOTER_LENGTH;
private int maxResultsPerPage = DEFAULT_MAX_RESULTS_PER_PAGE; private int maxResultsPerPage = DEFAULT_MAX_RESULTS_PER_PAGE;
@ -159,15 +163,30 @@ public final class MinecraftHelp<C> {
/** /**
* Sets a filter for what commands are visible inside the help menu. * Sets a filter for what commands are visible inside the help menu.
* When the {@link Predicate} tests <i>true</i>, then the command * When the {@link Predicate} tests {@code true}, then the command
* is included in the listings. * is included in the listings.
* <p>
* The default filter will return true for all commands.
* *
* @param commandPredicate Predicate to filter commands by * @param commandPredicate Predicate to filter commands by
* @since 1.4.0
*/ */
public void setCommandFilter(final @NonNull Predicate<Command<C>> commandPredicate) { public void commandFilter(final @NonNull Predicate<Command<C>> commandPredicate) {
this.commandFilter = commandPredicate; this.commandFilter = commandPredicate;
} }
/**
* Set the description decorator which will turn command and argument description strings into components.
* <p>
* The default decorator simply calls {@link Component#text(String)}
*
* @param decorator description decorator
* @since 1.4.0
*/
public void descriptionDecorator(final @NonNull Function<@NonNull String, @NonNull Component> decorator) {
this.descriptionDecorator = decorator;
}
/** /**
* Configure a message * Configure a message
* *
@ -189,6 +208,21 @@ public final class MinecraftHelp<C> {
* @param messageProvider The message provider to use * @param messageProvider The message provider to use
*/ */
public void setMessageProvider(final @NonNull BiFunction<C, String, String> messageProvider) { public void setMessageProvider(final @NonNull BiFunction<C, String, String> messageProvider) {
this.stringMessageProvider = messageProvider;
}
/**
* Set a custom message provider function to be used for getting messages from keys.
* <p>
* The keys are constants in {@link MinecraftHelp}.
* <p>
* This version of the method which takes a {@link MessageProvider} will have priority over a message provider
* registered through {@link #setMessageProvider(BiFunction)}
*
* @param messageProvider The message provider to use
* @since 1.4.0
*/
public void messageProvider(final @NonNull MessageProvider<C> messageProvider) {
this.messageProvider = messageProvider; this.messageProvider = messageProvider;
} }
@ -286,7 +320,8 @@ public final class MinecraftHelp<C> {
final Audience audience = this.getAudience(sender); final Audience audience = this.getAudience(sender);
audience.sendMessage(this.basicHeader(sender)); audience.sendMessage(this.basicHeader(sender));
audience.sendMessage(LinearComponents.linear( audience.sendMessage(LinearComponents.linear(
text(this.messageProvider.apply(sender, MESSAGE_NO_RESULTS_FOR_QUERY) + ": \"", this.colors.text), this.messageProvider.provide(sender, MESSAGE_NO_RESULTS_FOR_QUERY).color(this.colors.text),
text(": \"", this.colors.text),
this.highlight(text("/" + query, this.colors.highlight)), this.highlight(text("/" + query, this.colors.highlight)),
text("\"", this.colors.text) text("\"", this.colors.text)
)); ));
@ -312,18 +347,22 @@ public final class MinecraftHelp<C> {
header.add(this.showingResults(sender, query)); header.add(this.showingResults(sender, query));
header.add(text() header.add(text()
.append(this.lastBranch()) .append(this.lastBranch())
.append(text( .append(space())
String.format(" %s:", this.messageProvider.apply(sender, MESSAGE_AVAILABLE_COMMANDS)), .append(
this.colors.text this.messageProvider.provide(
)) sender,
MESSAGE_AVAILABLE_COMMANDS
).color(this.colors.text)
)
.append(text(":", this.colors.text))
.build() .build()
); );
return header; return header;
}, },
(helpEntry, isLastOfPage) -> { (helpEntry, isLastOfPage) -> {
final String description = helpEntry.getDescription().isEmpty() final Component description = helpEntry.getDescription().isEmpty()
? this.messageProvider.apply(sender, MESSAGE_CLICK_TO_SHOW_HELP) ? this.messageProvider.provide(sender, MESSAGE_CLICK_TO_SHOW_HELP)
: helpEntry.getDescription(); : this.descriptionDecorator.apply(helpEntry.getDescription());
final boolean lastBranch = final boolean lastBranch =
isLastOfPage || helpTopic.getEntries().indexOf(helpEntry) == helpTopic.getEntries().size() - 1; isLastOfPage || helpTopic.getEntries().indexOf(helpEntry) == helpTopic.getEntries().size() - 1;
@ -335,7 +374,7 @@ public final class MinecraftHelp<C> {
String.format(" /%s", helpEntry.getSyntaxString()), String.format(" /%s", helpEntry.getSyntaxString()),
this.colors.highlight this.colors.highlight
)) ))
.hoverEvent(text(description, this.colors.text)) .hoverEvent(description.color(this.colors.text))
.clickEvent(runCommand(this.commandPrefix + " " + helpEntry.getSyntaxString())) .clickEvent(runCommand(this.commandPrefix + " " + helpEntry.getSyntaxString()))
) )
.build(); .build();
@ -374,10 +413,8 @@ public final class MinecraftHelp<C> {
return ComponentHelper.repeat(space(), headerIndentation) return ComponentHelper.repeat(space(), headerIndentation)
.append(lastBranch ? this.lastBranch() : this.branch()) .append(lastBranch ? this.lastBranch() : this.branch())
.append(this.highlight(text(" /" + suggestion, this.colors.highlight)) .append(this.highlight(text(" /" + suggestion, this.colors.highlight))
.hoverEvent(text( .hoverEvent(this.messageProvider.provide(sender, MESSAGE_CLICK_TO_SHOW_HELP)
this.messageProvider.apply(sender, MESSAGE_CLICK_TO_SHOW_HELP), .color(this.colors.text))
this.colors.text
))
.clickEvent(runCommand(this.commandPrefix + " " + suggestion))); .clickEvent(runCommand(this.commandPrefix + " " + suggestion)));
}, },
(currentPage, maxPages) -> this.paginatedFooter(sender, currentPage, maxPages, query), (currentPage, maxPages) -> this.paginatedFooter(sender, currentPage, maxPages, query),
@ -397,24 +434,30 @@ public final class MinecraftHelp<C> {
.apply(helpTopic.getCommand().getArguments(), null); .apply(helpTopic.getCommand().getArguments(), null);
audience.sendMessage(text() audience.sendMessage(text()
.append(this.lastBranch()) .append(this.lastBranch())
.append(text(" " + this.messageProvider.apply(sender, MESSAGE_COMMAND) + ": ", this.colors.primary)) .append(space())
.append(this.messageProvider.provide(sender, MESSAGE_COMMAND).color(this.colors.primary))
.append(text(": ", this.colors.primary))
.append(this.highlight(text("/" + command, this.colors.highlight))) .append(this.highlight(text("/" + command, this.colors.highlight)))
); );
final String topicDescription = helpTopic.getDescription().isEmpty() final Component topicDescription = helpTopic.getDescription().isEmpty()
? this.messageProvider.apply(sender, MESSAGE_NO_DESCRIPTION) ? this.messageProvider.provide(sender, MESSAGE_NO_DESCRIPTION)
: helpTopic.getDescription(); : this.descriptionDecorator.apply(helpTopic.getDescription());
final boolean hasArguments = helpTopic.getCommand().getArguments().size() > 1; final boolean hasArguments = helpTopic.getCommand().getArguments().size() > 1;
audience.sendMessage(text() audience.sendMessage(text()
.append(text(" ")) .append(text(" "))
.append(hasArguments ? this.branch() : this.lastBranch()) .append(hasArguments ? this.branch() : this.lastBranch())
.append(text(" " + this.messageProvider.apply(sender, MESSAGE_DESCRIPTION) + ": ", this.colors.primary)) .append(space())
.append(text(topicDescription, this.colors.text)) .append(this.messageProvider.provide(sender, MESSAGE_DESCRIPTION).color(this.colors.primary))
.append(text(": ", this.colors.primary))
.append(topicDescription.color(this.colors.text))
); );
if (hasArguments) { if (hasArguments) {
audience.sendMessage(text() audience.sendMessage(text()
.append(text(" ")) .append(text(" "))
.append(this.lastBranch()) .append(this.lastBranch())
.append(text(" " + this.messageProvider.apply(sender, MESSAGE_ARGUMENTS) + ":", this.colors.primary)) .append(space())
.append(this.messageProvider.provide(sender, MESSAGE_ARGUMENTS).color(this.colors.primary))
.append(text(":", this.colors.primary))
); );
final Iterator<CommandComponent<C>> iterator = helpTopic.getCommand().getComponents().iterator(); final Iterator<CommandComponent<C>> iterator = helpTopic.getCommand().getComponents().iterator();
@ -433,15 +476,16 @@ public final class MinecraftHelp<C> {
.append(iterator.hasNext() ? this.branch() : this.lastBranch()) .append(iterator.hasNext() ? this.branch() : this.lastBranch())
.append(this.highlight(text(" " + syntax, this.colors.highlight))); .append(this.highlight(text(" " + syntax, this.colors.highlight)));
if (!argument.isRequired()) { if (!argument.isRequired()) {
textComponent.append(text( textComponent.append(text(" (", this.colors.alternateHighlight));
" (" + this.messageProvider.apply(sender, MESSAGE_OPTIONAL) + ")", textComponent.append(
this.colors.alternateHighlight this.messageProvider.provide(sender, MESSAGE_OPTIONAL).color(this.colors.alternateHighlight)
)); );
textComponent.append(text(")", this.colors.alternateHighlight));
} }
final String description = component.getDescription().getDescription(); final String description = component.getDescription().getDescription();
if (!description.isEmpty()) { if (!description.isEmpty()) {
textComponent.append(text(" - ", this.colors.accent)); textComponent.append(text(" - ", this.colors.accent));
textComponent.append(text(description, this.colors.text)); textComponent.append(this.descriptionDecorator.apply(description).color(this.colors.text));
} }
audience.sendMessage(textComponent); audience.sendMessage(textComponent);
@ -455,7 +499,8 @@ public final class MinecraftHelp<C> {
final @NonNull String query final @NonNull String query
) { ) {
return text() return text()
.append(text(this.messageProvider.apply(sender, MESSAGE_SHOWING_RESULTS_FOR_QUERY) + ": \"", this.colors.text)) .append(this.messageProvider.provide(sender, MESSAGE_SHOWING_RESULTS_FOR_QUERY).color(this.colors.text))
.append(text(": \"", this.colors.text))
.append(this.highlight(text("/" + query, this.colors.highlight))) .append(this.highlight(text("/" + query, this.colors.highlight)))
.append(text("\"", this.colors.text)) .append(text("\"", this.colors.text))
.build(); .build();
@ -464,7 +509,7 @@ public final class MinecraftHelp<C> {
private @NonNull Component button( private @NonNull Component button(
final char icon, final char icon,
final @NonNull String command, final @NonNull String command,
final @NonNull String hoverText final @NonNull Component hoverText
) { ) {
return text() return text()
.append(space()) .append(space())
@ -473,7 +518,7 @@ public final class MinecraftHelp<C> {
.append(text(']', this.colors.accent)) .append(text(']', this.colors.accent))
.append(space()) .append(space())
.clickEvent(runCommand(command)) .clickEvent(runCommand(command))
.hoverEvent(text(hoverText, this.colors.text)) .hoverEvent(hoverText)
.build(); .build();
} }
@ -496,7 +541,7 @@ public final class MinecraftHelp<C> {
final String nextPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage + 1); final String nextPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage + 1);
final Component nextPageButton = this.button('→', nextPageCommand, final Component nextPageButton = this.button('→', nextPageCommand,
this.messageProvider.apply(sender, MESSAGE_CLICK_FOR_NEXT_PAGE) this.messageProvider.provide(sender, MESSAGE_CLICK_FOR_NEXT_PAGE).color(this.colors.text)
); );
if (firstPage) { if (firstPage) {
return this.header(sender, nextPageButton); return this.header(sender, nextPageButton);
@ -504,7 +549,7 @@ public final class MinecraftHelp<C> {
final String previousPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage - 1); final String previousPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage - 1);
final Component previousPageButton = this.button('←', previousPageCommand, final Component previousPageButton = this.button('←', previousPageCommand,
this.messageProvider.apply(sender, MESSAGE_CLICK_FOR_PREVIOUS_PAGE) this.messageProvider.provide(sender, MESSAGE_CLICK_FOR_PREVIOUS_PAGE).color(this.colors.text)
); );
if (lastPage) { if (lastPage) {
return this.header(sender, previousPageButton); return this.header(sender, previousPageButton);
@ -530,9 +575,10 @@ public final class MinecraftHelp<C> {
} }
private @NonNull Component basicHeader(final @NonNull C sender) { private @NonNull Component basicHeader(final @NonNull C sender) {
return this.header(sender, text( return this.header(sender, LinearComponents.linear(
" " + this.messageProvider.apply(sender, MESSAGE_HELP_TITLE) + " ", space(),
this.colors.highlight this.messageProvider.provide(sender, MESSAGE_HELP_TITLE).color(this.colors.highlight),
space()
)); ));
} }
@ -542,10 +588,9 @@ public final class MinecraftHelp<C> {
final int pages final int pages
) { ) {
return this.header(sender, text() return this.header(sender, text()
.append(text( .append(space())
" " + this.messageProvider.apply(sender, MESSAGE_HELP_TITLE) + " ", .append(this.messageProvider.provide(sender, MESSAGE_HELP_TITLE).color(this.colors.highlight))
this.colors.highlight .append(space())
))
.append(text("(", this.colors.alternateHighlight)) .append(text("(", this.colors.alternateHighlight))
.append(text(currentPage, this.colors.text)) .append(text(currentPage, this.colors.text))
.append(text("/", this.colors.alternateHighlight)) .append(text("/", this.colors.alternateHighlight))
@ -580,12 +625,38 @@ public final class MinecraftHelp<C> {
final int attemptedPage, final int attemptedPage,
final int maxPages final int maxPages
) { ) {
return this.highlight(text( return this.highlight(
this.messageProvider.apply(sender, MESSAGE_PAGE_OUT_OF_RANGE) this.messageProvider.provide(
.replace("<page>", String.valueOf(attemptedPage)) sender,
.replace("<max_pages>", String.valueOf(maxPages)), MESSAGE_PAGE_OUT_OF_RANGE,
this.colors.text String.valueOf(attemptedPage),
)); String.valueOf(maxPages)
)
.color(this.colors.text)
.replaceText(config -> {
config.matchLiteral("<page>");
config.replacement(String.valueOf(attemptedPage));
})
.replaceText(config -> {
config.matchLiteral("<max_pages>");
config.replacement(String.valueOf(maxPages));
})
);
}
@FunctionalInterface
public interface MessageProvider<C> {
/**
* Creates a component from a command sender, key, and arguments
*
* @param sender command sender
* @param key message key (constants in {@link MinecraftHelp}
* @param args args
* @return component
*/
@NonNull Component provide(@NonNull C sender, @NonNull String key, @NonNull String... args);
} }
/** /**