Merge pull request #3010 from Multiverse/ben/mv5/nested-locale-message

Add support for nested Message within MessageReplacement and fix some checkstyles
This commit is contained in:
Jeremy Wood 2023-09-11 08:30:09 -04:00 committed by GitHub
commit 5a474d7efa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 164 additions and 20 deletions

View File

@ -35,7 +35,8 @@ public class MVCommandIssuer extends OpenBukkitCommandIssuer {
private void sendMessage(MessageType messageType, Message message) { private void sendMessage(MessageType messageType, Message message) {
if (message instanceof MessageKeyProvider) { if (message instanceof MessageKeyProvider) {
sendMessage(messageType, (MessageKeyProvider) message, message.getReplacements()); sendMessage(messageType, (MessageKeyProvider) message,
message.getReplacements(getManager().getLocales(), this));
} else { } else {
var formatter = getManager().getFormat(messageType); var formatter = getManager().getFormat(messageType);
if (formatter != null) { if (formatter != null) {

View File

@ -58,9 +58,12 @@ public class MVCommandManager extends PaperCommandManager {
} }
} }
/**
* {@inheritDoc}
*/
@Override @Override
public BukkitLocales getLocales() { public PluginLocales getLocales() {
return this.locales; return (PluginLocales) this.locales;
} }
/** /**

View File

@ -14,6 +14,31 @@ public class MultiverseException extends Exception {
private final @Nullable Message message; private final @Nullable Message message;
/**
* Creates a new exception with the given message.
* <br/>
* If the message is not null, this exception will also contain a {@link Message} which can be accessed via
* {@link #getMVMessage()}. This message will just be the given message wrapped in a {@link Message}.
*
* @param message The message for the exception
*/
public MultiverseException(@Nullable String message) {
this(message != null ? Message.of(message) : null);
}
/**
* Creates a new exception with the given message.
* <br/>
* If the message is not null, this exception will also contain a String message which can be accessed via
* {@link #getMessage()}. This message will just be the given message formatted without locale support.
*
* @param message The message for the exception
*/
public MultiverseException(@Nullable Message message) {
super(message != null ? message.formatted() : null);
this.message = message;
}
/** /**
* Creates a new exception with the given message and cause. * Creates a new exception with the given message and cause.
* <br/> * <br/>

View File

@ -17,8 +17,7 @@ final class LocalizedMessage extends Message implements MessageKeyProvider {
LocalizedMessage( LocalizedMessage(
@NotNull MessageKeyProvider messageKeyProvider, @NotNull MessageKeyProvider messageKeyProvider,
@NotNull String message, @NotNull String message,
@NotNull MessageReplacement... replacements @NotNull MessageReplacement... replacements) {
) {
super(message, replacements); super(message, replacements);
this.messageKeyProvider = messageKeyProvider; this.messageKeyProvider = messageKeyProvider;
} }
@ -28,13 +27,34 @@ final class LocalizedMessage extends Message implements MessageKeyProvider {
return messageKeyProvider.getMessageKey(); return messageKeyProvider.getMessageKey();
} }
@Override
public @NotNull String[] getReplacements(@NotNull PluginLocales locales, @Nullable CommandIssuer commandIssuer) {
return toReplacementsArray(locales, commandIssuer, replacements);
}
@Override @Override
public @NotNull String formatted(@NotNull PluginLocales locales, @Nullable CommandIssuer commandIssuer) { public @NotNull String formatted(@NotNull PluginLocales locales, @Nullable CommandIssuer commandIssuer) {
Objects.requireNonNull(locales, "locales must not be null"); Objects.requireNonNull(locales, "locales must not be null");
if (getReplacements().length == 0) { String[] parsedReplacements = getReplacements(locales, commandIssuer);
return raw(); if (parsedReplacements.length == 0) {
return locales.getMessage(commandIssuer, getMessageKey());
} }
return ACFUtil.replaceStrings(locales.getMessage(commandIssuer, getMessageKey()), getReplacements()); return ACFUtil.replaceStrings(locales.getMessage(commandIssuer, getMessageKey()), parsedReplacements);
}
private static String[] toReplacementsArray(
@NotNull PluginLocales locales,
@Nullable CommandIssuer commandIssuer,
@NotNull MessageReplacement... replacements) {
String[] replacementsArray = new String[replacements.length * 2];
int i = 0;
for (MessageReplacement replacement : replacements) {
replacementsArray[i++] = replacement.getKey();
replacementsArray[i++] = replacement.getReplacement().fold(
str -> str,
message -> message.formatted(locales, commandIssuer));
}
return replacementsArray;
} }
} }

View File

@ -48,8 +48,7 @@ public sealed class Message permits LocalizedMessage {
public static Message of( public static Message of(
@NotNull MessageKeyProvider messageKeyProvider, @NotNull MessageKeyProvider messageKeyProvider,
@NotNull String nonLocalizedMessage, @NotNull String nonLocalizedMessage,
@NotNull MessageReplacement... replacements @NotNull MessageReplacement... replacements) {
) {
Objects.requireNonNull(messageKeyProvider, "messageKeyProvider must not be null"); Objects.requireNonNull(messageKeyProvider, "messageKeyProvider must not be null");
Objects.requireNonNull(nonLocalizedMessage, "message must not be null"); Objects.requireNonNull(nonLocalizedMessage, "message must not be null");
for (MessageReplacement replacement : replacements) { for (MessageReplacement replacement : replacements) {
@ -60,11 +59,11 @@ public sealed class Message permits LocalizedMessage {
} }
private final @NotNull String message; private final @NotNull String message;
private final @NotNull String[] replacements; protected final @NotNull MessageReplacement[] replacements;
protected Message(@NotNull String message, @NotNull MessageReplacement... replacements) { protected Message(@NotNull String message, @NotNull MessageReplacement... replacements) {
this.message = message; this.message = message;
this.replacements = toReplacementsArray(replacements); this.replacements = replacements;
} }
/** /**
@ -76,7 +75,21 @@ public sealed class Message permits LocalizedMessage {
* @return The replacements * @return The replacements
*/ */
public @NotNull String[] getReplacements() { public @NotNull String[] getReplacements() {
return replacements; return toReplacementsArray(replacements);
}
/**
* Gets the replacements for this message with localization support.
* <br/>
* This array is guaranteed to be of even length and suitable for use with
* {@link ACFUtil#replaceStrings(String, String...)}.
*
* @param locales The MultiverseCore locales provider
* @param commandIssuer The command issuer the message is for, or null for the console (default locale)
* @return The replacements
*/
public @NotNull String[] getReplacements(@NotNull PluginLocales locales, @Nullable CommandIssuer commandIssuer) {
return getReplacements();
} }
/** /**
@ -96,10 +109,11 @@ public sealed class Message permits LocalizedMessage {
* @return The formatted message * @return The formatted message
*/ */
public @NotNull String formatted() { public @NotNull String formatted() {
if (replacements.length == 0) { String[] parsedReplacements = getReplacements();
if (parsedReplacements.length == 0) {
return raw(); return raw();
} }
return ACFUtil.replaceStrings(message, replacements); return ACFUtil.replaceStrings(message, parsedReplacements);
} }
/** /**
@ -133,7 +147,7 @@ public sealed class Message permits LocalizedMessage {
int i = 0; int i = 0;
for (MessageReplacement replacement : replacements) { for (MessageReplacement replacement : replacements) {
replacementsArray[i++] = replacement.getKey(); replacementsArray[i++] = replacement.getKey();
replacementsArray[i++] = replacement.getReplacement(); replacementsArray[i++] = replacement.getReplacement().fold(s -> s, Message::formatted);
} }
return replacementsArray; return replacementsArray;
} }

View File

@ -1,5 +1,6 @@
package com.onarandombox.MultiverseCore.utils.message; package com.onarandombox.MultiverseCore.utils.message;
import io.vavr.control.Either;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -7,7 +8,7 @@ import org.jetbrains.annotations.Nullable;
/** /**
* Captures string replacements for {@link Message}s. * Captures string replacements for {@link Message}s.
*/ */
public final class MessageReplacement { public final class MessageReplacement {
/** /**
* Creates a replacement key for the given key string. * Creates a replacement key for the given key string.
@ -20,6 +21,9 @@ import org.jetbrains.annotations.Nullable;
return new MessageReplacement.Key(key); return new MessageReplacement.Key(key);
} }
/**
* A replacement key that maps to a value it can be replaced with.
*/
public static final class Key { public static final class Key {
private final @NotNull String key; private final @NotNull String key;
@ -28,6 +32,17 @@ import org.jetbrains.annotations.Nullable;
this.key = key; this.key = key;
} }
/**
* Creates a replacement for this key.
*
* @param replacement The replacement message
* @return A new message replacement
*/
@Contract(value = "_ -> new", pure = true)
public MessageReplacement with(@NotNull Message replacement) {
return new MessageReplacement(key, replacement);
}
/** /**
* Creates a replacement for this key. * Creates a replacement for this key.
* *
@ -41,11 +56,16 @@ import org.jetbrains.annotations.Nullable;
} }
private final @NotNull String key; private final @NotNull String key;
private final @NotNull String replacement; private final @NotNull Either<String, Message> replacement;
private MessageReplacement(@NotNull String key, @NotNull Message replacement) {
this.key = key;
this.replacement = Either.right(replacement);
}
private MessageReplacement(@NotNull String key, @Nullable Object replacement) { private MessageReplacement(@NotNull String key, @Nullable Object replacement) {
this.key = key; this.key = key;
this.replacement = String.valueOf(replacement); this.replacement = Either.left(String.valueOf(replacement));
} }
/** /**
@ -62,7 +82,7 @@ import org.jetbrains.annotations.Nullable;
* *
* @return The replacement * @return The replacement
*/ */
public @NotNull String getReplacement() { public @NotNull Either<String, Message> getReplacement() {
return replacement; return replacement;
} }
} }

View File

@ -0,0 +1 @@
package com.onarandombox.MultiverseCore.utils.message;

View File

@ -253,4 +253,64 @@ class LocalizationTest : TestWithMockBukkit() {
} }
} }
} }
@Nested
inner class WithMessagesAsReplacement {
private val replacementKey = "{world}"
private val replacementValue = Message.of(MVCorei18n.GENERIC_SUCCESS, "success")
private val messageString = "Hello $replacementKey!"
private val replacedMessageString = "Hello success!"
private val replacedMessageStringLocale = "Cloned world 'Success!'!"
private val message = MVCorei18n.CLONE_SUCCESS
.bundle(messageString, replace(replacementKey).with(replacementValue))
@Test
fun `The raw message should be the same as the original`() {
assertEquals(messageString, message.raw())
}
@Test
fun `The formatted message should be the replaced original string`() {
assertEquals(replacedMessageString, message.formatted())
}
@Test
fun `The formatted message with PluginLocales should be different from the replaced original string`() {
assertEquals(replacedMessageStringLocale, message.formatted(locales))
}
@Test
fun `The formatted message with PluginLocales should have performed replacement`() {
assertThat(message.formatted(locales), !containsSubstring(replacementKey))
assertThat(message.formatted(locales), containsSubstring("Success!"))
}
@Nested
@DisplayName("And a command sender is provided")
inner class WithCommandSender {
private lateinit var sender: CommandSender
private lateinit var issuer: MVCommandIssuer
@BeforeTest
fun setUp() {
sender = spy(Bukkit.getConsoleSender())
issuer = commandManager.getCommandIssuer(sender)
}
@Test
fun `Sending the issuer the message should send the formatted message to the sender`() {
issuer.sendInfo(message);
val sentMessage = argumentCaptor<String> {
verify(sender).sendMessage(capture())
}.firstValue
assertNotEquals(replacedMessageString, sentMessage)
assertThat(sentMessage, !containsSubstring(replacementKey))
assertThat(sentMessage, containsSubstring(replacedMessageStringLocale))
}
}
}
} }