From 2ddf0d8da462464ba435cdb60a97123df3e5cf06 Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Tue, 19 Sep 2023 13:08:48 +0100 Subject: [PATCH 1/5] initial thoughts on a MiniMessageTranslator --- .../minimessage/translation/ArgumentTag.java | 39 +++++++++++ .../translation/MiniMessageTranslator.java | 69 +++++++++++++++++++ .../minimessage/translation/package-info.java | 27 ++++++++ 3 files changed, 135 insertions(+) create mode 100644 text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java create mode 100644 text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java create mode 100644 text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java new file mode 100644 index 000000000..a7adce902 --- /dev/null +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java @@ -0,0 +1,39 @@ +package net.kyori.adventure.text.minimessage.translation; + +import java.util.List; +import java.util.Objects; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.minimessage.ParsingException; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ArgumentTag implements TagResolver { + private static final String NAME = "argument"; + private static final String NAME_1 = "arg"; + + private final List argumentComponents; + + public ArgumentTag(final @NotNull List argumentComponents) { + this.argumentComponents = Objects.requireNonNull(argumentComponents, "argumentComponents"); + } + + @Override + public @Nullable Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException { + final int index = arguments.popOr("No argument number provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments)); + + if (index < 0 || index >= argumentComponents.size()) { + throw ctx.newException("Invalid argument number", arguments); + } + + return Tag.inserting(argumentComponents.get(index)); + } + + @Override + public boolean has(final @NotNull String name) { + return name.equals(NAME) || name.equals(NAME_1); + } +} diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java new file mode 100644 index 000000000..f864b528a --- /dev/null +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java @@ -0,0 +1,69 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage.translation; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Objects; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.translation.Translator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class MiniMessageTranslator implements Translator { + + final MiniMessage miniMessage; + + public MiniMessageTranslator() { + this(MiniMessage.miniMessage()); + } + + public MiniMessageTranslator(final @NotNull MiniMessage miniMessage) { + this.miniMessage = Objects.requireNonNull(miniMessage, "miniMessage"); + } + + protected abstract @Nullable String getMiniMessageString(final @NotNull String key, final @NotNull Locale locale); + + @Override + public final @Nullable MessageFormat translate(final @NotNull String key, final @NotNull Locale locale) { + return null; + } + + @Override + public @Nullable Component translate(final @NotNull TranslatableComponent component, final @NotNull Locale locale) { + final String miniMessageString = getMiniMessageString(component.key(), locale); + + if (miniMessageString == null) { + return null; + } + + if (component.args().isEmpty()) { + return MiniMessage.miniMessage().deserialize(miniMessageString); + } else { + return MiniMessage.miniMessage().deserialize(miniMessageString, new ArgumentTag(component.args())); + } + } +} diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java new file mode 100644 index 000000000..e592ad329 --- /dev/null +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java @@ -0,0 +1,27 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * Tools for working with Adventure's translation API. + */ +package net.kyori.adventure.text.minimessage.translation; From ca373c0598e7164776bc89d5ac4ca9b30ddacdd1 Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Tue, 19 Sep 2023 13:12:23 +0100 Subject: [PATCH 2/5] copy children over too --- .../translation/MiniMessageTranslator.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java index f864b528a..627c1ccb3 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java @@ -60,10 +60,18 @@ public MiniMessageTranslator(final @NotNull MiniMessage miniMessage) { return null; } + final Component resultingComponent; + if (component.args().isEmpty()) { - return MiniMessage.miniMessage().deserialize(miniMessageString); + resultingComponent = MiniMessage.miniMessage().deserialize(miniMessageString); + } else { + resultingComponent = MiniMessage.miniMessage().deserialize(miniMessageString, new ArgumentTag(component.args())); + } + + if (component.children().isEmpty()) { + return resultingComponent; } else { - return MiniMessage.miniMessage().deserialize(miniMessageString, new ArgumentTag(component.args())); + return resultingComponent.children(component.children()); } } } From 9380716c01d9bb9c312055788bd53e315b55c67c Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Mon, 6 Jan 2025 17:37:54 +0000 Subject: [PATCH 3/5] update with named arguments and documentation --- .../minimessage/translation/ArgumentTag.java | 66 +++++++++-- .../translation/MiniMessageTranslator.java | 64 ++++++++-- .../translation/NamedTranslationArgument.java | 111 ++++++++++++++++++ .../NamedTranslationArgumentImpl.java | 55 +++++++++ 4 files changed, 279 insertions(+), 17 deletions(-) create mode 100644 text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java create mode 100644 text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java index a7adce902..4e0db7c6b 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java @@ -1,6 +1,33 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package net.kyori.adventure.text.minimessage.translation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.minimessage.Context; @@ -11,29 +38,50 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class ArgumentTag implements TagResolver { +final class ArgumentTag implements TagResolver { private static final String NAME = "argument"; private static final String NAME_1 = "arg"; private final List argumentComponents; + private final Map namedArguments; - public ArgumentTag(final @NotNull List argumentComponents) { - this.argumentComponents = Objects.requireNonNull(argumentComponents, "argumentComponents"); + ArgumentTag(final @NotNull List argumentComponents) { + this.argumentComponents = new ArrayList<>(Objects.requireNonNull(argumentComponents, "argumentComponents")); + + final Map namedArgumentMap = new HashMap<>(this.argumentComponents.size()); + for (final ComponentLike argument : this.argumentComponents) { + if (argument instanceof NamedTranslationArgument) { + final NamedTranslationArgument namedArgument = (NamedTranslationArgument) argument; + namedArgumentMap.put(namedArgument.name(), namedArgument); + } + } + + this.namedArguments = Collections.unmodifiableMap(namedArgumentMap); } @Override public @Nullable Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException { - final int index = arguments.popOr("No argument number provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments)); + if (name.equals(NAME) || name.equals(NAME_1)) { + final int index = arguments.popOr("No argument number provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments)); - if (index < 0 || index >= argumentComponents.size()) { - throw ctx.newException("Invalid argument number", arguments); - } + if (index < 0 || index >= this.argumentComponents.size()) { + throw ctx.newException("Invalid argument number", arguments); + } + + return Tag.inserting(this.argumentComponents.get(index)); + } else { + final NamedTranslationArgument namedArgument = this.namedArguments.get(name); - return Tag.inserting(argumentComponents.get(index)); + if (namedArgument != null) { + return Tag.inserting(namedArgument); + } else { + return null; + } + } } @Override public boolean has(final @NotNull String name) { - return name.equals(NAME) || name.equals(NAME_1); + return name.equals(NAME) || name.equals(NAME_1) || this.namedArguments.containsKey(name); } } diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java index 627c1ccb3..856bb6e3b 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/MiniMessageTranslator.java @@ -1,7 +1,7 @@ /* * This file is part of adventure, licensed under the MIT License. * - * Copyright (c) 2017-2022 KyoriPowered + * Copyright (c) 2017-2025 KyoriPowered * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,22 +29,70 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.translation.GlobalTranslator; import net.kyori.adventure.translation.Translator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +/** + * A {@link Translator} implementation that translates strings using MiniMessage. + * + *

To use this feature, you should extend this class, implementing the + * {@link #getMiniMessageString(String, Locale)} method to return the MiniMessage string + * for a given key and locale. + * After that, you can use the translator as-is using + * {@link #translate(TranslatableComponent, Locale)}, or automatically (depending on the + * implementing platform) using the {@link GlobalTranslator}.

+ * + *

This system supports arguments using {@code } tags (or {@code argument}, + * where {@code 0} is the index of the argument to use). + * Alternatively, you can use named arguments by creating the translatable component + * with {@link NamedTranslationArgument} as the arguments. + * The provided {@link NamedTranslationArgument#name() name} will be available for use in + * a tag as {@code }, in addition to the index-based {@code arg} tag.

+ * + * @see Translator + * @see GlobalTranslator + * @see NamedTranslationArgument + * @since 4.19.0 + */ public abstract class MiniMessageTranslator implements Translator { + private final MiniMessage miniMessage; - final MiniMessage miniMessage; - + /** + * Constructor for a MiniMessageTranslator using the default MiniMessage instance. + * + * @see MiniMessage#miniMessage() + * @since 4.19.0 + */ public MiniMessageTranslator() { this(MiniMessage.miniMessage()); } + /** + * Constructor for a MiniMessageTranslator using a specific MiniMessage instance. + * + * @param miniMessage the MiniMessage instance + * @see MiniMessage#miniMessage() + * @since 4.19.0 + */ public MiniMessageTranslator(final @NotNull MiniMessage miniMessage) { this.miniMessage = Objects.requireNonNull(miniMessage, "miniMessage"); } + /** + * Returns a raw MiniMessage string for the given key. + * + *

If no string is found for the given key, returning {@code null} will use the + * {@link TranslatableComponent#fallback() translatable component's fallback} (or the + * key itself).

+ * + * @param key the key + * @param locale the locale + * @return the resulting MiniMessage string + * @since 4.19.0 + */ + @SuppressWarnings("checkstyle:MethodName") protected abstract @Nullable String getMiniMessageString(final @NotNull String key, final @NotNull Locale locale); @Override @@ -53,8 +101,8 @@ public MiniMessageTranslator(final @NotNull MiniMessage miniMessage) { } @Override - public @Nullable Component translate(final @NotNull TranslatableComponent component, final @NotNull Locale locale) { - final String miniMessageString = getMiniMessageString(component.key(), locale); + public final @Nullable Component translate(final @NotNull TranslatableComponent component, final @NotNull Locale locale) { + final String miniMessageString = this.getMiniMessageString(component.key(), locale); if (miniMessageString == null) { return null; @@ -62,10 +110,10 @@ public MiniMessageTranslator(final @NotNull MiniMessage miniMessage) { final Component resultingComponent; - if (component.args().isEmpty()) { - resultingComponent = MiniMessage.miniMessage().deserialize(miniMessageString); + if (component.arguments().isEmpty()) { + resultingComponent = this.miniMessage.deserialize(miniMessageString); } else { - resultingComponent = MiniMessage.miniMessage().deserialize(miniMessageString, new ArgumentTag(component.args())); + resultingComponent = this.miniMessage.deserialize(miniMessageString, new ArgumentTag(component.arguments())); } if (component.children().isEmpty()) { diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java new file mode 100644 index 000000000..317a8b487 --- /dev/null +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java @@ -0,0 +1,111 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage.translation; + +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.TranslationArgument; +import net.kyori.adventure.text.TranslationArgumentLike; +import net.kyori.adventure.text.minimessage.tag.TagPattern; +import org.jetbrains.annotations.NotNull; + +import static java.util.Objects.requireNonNull; + +/** + * A {@link TranslationArgument} with an associated string name. + * + *

This is intended for use with {@link TranslatableComponent translatable components} + * used with a {@link MiniMessageTranslator} instance to allow {@code } tags.

+ * + * @since 4.19.0 + */ +public interface NamedTranslationArgument extends TranslationArgumentLike { + /** + * Create a named boolean argument. + * + * @param name the name + * @param value the value + * @return the named argument + * @since 4.19.0 + */ + static @NotNull NamedTranslationArgument bool(final @TagPattern @NotNull String name, final boolean value) { + return argument(name, TranslationArgument.bool(value)); + } + + /** + * Create a named numeric argument. + * + * @param name the name + * @param value the value + * @return the named argument + * @since 4.19.0 + */ + static @NotNull NamedTranslationArgument numeric(final @TagPattern @NotNull String name, final @NotNull Number value) { + return argument(name, TranslationArgument.numeric(value)); + } + + /** + * Create a named component argument. + * + * @param name the name + * @param value the value + * @return the named argument + * @since 4.19.0 + */ + static @NotNull NamedTranslationArgument component(final @TagPattern @NotNull String name, final @NotNull ComponentLike value) { + return argument(name, TranslationArgument.component(value)); + } + + /** + * Create a named translation argument. + * + * @param name the name + * @param argument the translation argument + * @return the named argument + * @since 4.19.0 + */ + static @NotNull NamedTranslationArgument argument(final @TagPattern @NotNull String name, final @NotNull TranslationArgumentLike argument) { + return argument(name, requireNonNull(argument, "argument").asTranslationArgument()); + } + + /** + * Create a named translation argument. + * + * @param name the name + * @param argument the translation argument + * @return the named argument + * @since 4.19.0 + */ + static @NotNull NamedTranslationArgument argument(final @TagPattern @NotNull String name, final @NotNull TranslationArgument argument) { + return new NamedTranslationArgumentImpl(name, argument); + } + + /** + * The name of this translation argument. + * + * @return the name + * @since 4.19.0 + */ + @TagPattern @NotNull String name(); +} diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java new file mode 100644 index 000000000..5873e4a15 --- /dev/null +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java @@ -0,0 +1,55 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage.translation; + +import java.util.Objects; +import net.kyori.adventure.text.TranslationArgument; +import net.kyori.adventure.text.minimessage.internal.TagInternals; +import net.kyori.adventure.text.minimessage.tag.TagPattern; +import org.jetbrains.annotations.NotNull; + +final class NamedTranslationArgumentImpl implements NamedTranslationArgument { + + private final @TagPattern @NotNull String name; + private final @NotNull TranslationArgument argument; + + NamedTranslationArgumentImpl(final @TagPattern @NotNull String name, final @NotNull TranslationArgument argument) { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(argument, "argument"); + TagInternals.assertValidTagName(name); + + this.name = name; + this.argument = argument; + } + + @Override + public @TagPattern @NotNull String name() { + return this.name; + } + + @Override + public @NotNull TranslationArgument asTranslationArgument() { + return this.argument; + } +} From 51ba2d14921bed37f2dd5473afa68bd63a97d30b Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Mon, 6 Jan 2025 17:39:18 +0000 Subject: [PATCH 4/5] update year in package info --- .../adventure/text/minimessage/translation/package-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java index e592ad329..0a6f9874c 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/package-info.java @@ -1,7 +1,7 @@ /* * This file is part of adventure, licensed under the MIT License. * - * Copyright (c) 2017-2022 KyoriPowered + * Copyright (c) 2017-2025 KyoriPowered * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From d504ba7829792f0cd5f92e0c76f3b4cbd0910b8a Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Mon, 6 Jan 2025 18:07:53 +0000 Subject: [PATCH 5/5] and now we go virtual --- .../minimessage/translation/ArgumentTag.java | 18 ++++++---- .../translation/NamedTranslationArgument.java | 34 +++++++++++++++---- .../NamedTranslationArgumentImpl.java | 23 ++++++++++--- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java index 4e0db7c6b..9f9eebb88 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/ArgumentTag.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Objects; import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.VirtualComponent; +import net.kyori.adventure.text.VirtualComponentRenderer; import net.kyori.adventure.text.minimessage.Context; import net.kyori.adventure.text.minimessage.ParsingException; import net.kyori.adventure.text.minimessage.tag.Tag; @@ -43,16 +45,20 @@ final class ArgumentTag implements TagResolver { private static final String NAME_1 = "arg"; private final List argumentComponents; - private final Map namedArguments; + private final Map namedArguments; ArgumentTag(final @NotNull List argumentComponents) { this.argumentComponents = new ArrayList<>(Objects.requireNonNull(argumentComponents, "argumentComponents")); - final Map namedArgumentMap = new HashMap<>(this.argumentComponents.size()); + final Map namedArgumentMap = new HashMap<>(this.argumentComponents.size()); for (final ComponentLike argument : this.argumentComponents) { - if (argument instanceof NamedTranslationArgument) { - final NamedTranslationArgument namedArgument = (NamedTranslationArgument) argument; - namedArgumentMap.put(namedArgument.name(), namedArgument); + if (argument instanceof VirtualComponent) { + final VirtualComponentRenderer renderer = ((VirtualComponent) argument).renderer(); + + if (renderer instanceof NamedTranslationArgument) { + final NamedTranslationArgument namedArgument = (NamedTranslationArgument) argument; + namedArgumentMap.put(namedArgument.name(), namedArgument.translationArgument()); + } } } @@ -70,7 +76,7 @@ final class ArgumentTag implements TagResolver { return Tag.inserting(this.argumentComponents.get(index)); } else { - final NamedTranslationArgument namedArgument = this.namedArguments.get(name); + final ComponentLike namedArgument = this.namedArguments.get(name); if (namedArgument != null) { return Tag.inserting(namedArgument); diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java index 317a8b487..c5d98ba0f 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgument.java @@ -23,11 +23,14 @@ */ package net.kyori.adventure.text.minimessage.translation; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.TranslationArgument; import net.kyori.adventure.text.TranslationArgumentLike; +import net.kyori.adventure.text.VirtualComponent; import net.kyori.adventure.text.minimessage.tag.TagPattern; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import static java.util.Objects.requireNonNull; @@ -38,9 +41,18 @@ *

This is intended for use with {@link TranslatableComponent translatable components} * used with a {@link MiniMessageTranslator} instance to allow {@code } tags.

* + *

Static methods on this class work by creating + * {@link VirtualComponent virtual components} that store instances of this class. + * The MiniMessage translator instance detects these virtual components to use the name + * provided as tag names to replace the {@code } tag.

+ * + *

As the names provided to all static methods in this class are used to create tags, + * they must be valid tag names.

+ * * @since 4.19.0 */ -public interface NamedTranslationArgument extends TranslationArgumentLike { +@ApiStatus.NonExtendable +public interface NamedTranslationArgument { /** * Create a named boolean argument. * @@ -49,7 +61,7 @@ public interface NamedTranslationArgument extends TranslationArgumentLike { * @return the named argument * @since 4.19.0 */ - static @NotNull NamedTranslationArgument bool(final @TagPattern @NotNull String name, final boolean value) { + static @NotNull ComponentLike bool(final @TagPattern @NotNull String name, final boolean value) { return argument(name, TranslationArgument.bool(value)); } @@ -61,7 +73,7 @@ public interface NamedTranslationArgument extends TranslationArgumentLike { * @return the named argument * @since 4.19.0 */ - static @NotNull NamedTranslationArgument numeric(final @TagPattern @NotNull String name, final @NotNull Number value) { + static @NotNull ComponentLike numeric(final @TagPattern @NotNull String name, final @NotNull Number value) { return argument(name, TranslationArgument.numeric(value)); } @@ -73,7 +85,7 @@ public interface NamedTranslationArgument extends TranslationArgumentLike { * @return the named argument * @since 4.19.0 */ - static @NotNull NamedTranslationArgument component(final @TagPattern @NotNull String name, final @NotNull ComponentLike value) { + static @NotNull ComponentLike component(final @TagPattern @NotNull String name, final @NotNull ComponentLike value) { return argument(name, TranslationArgument.component(value)); } @@ -85,7 +97,7 @@ public interface NamedTranslationArgument extends TranslationArgumentLike { * @return the named argument * @since 4.19.0 */ - static @NotNull NamedTranslationArgument argument(final @TagPattern @NotNull String name, final @NotNull TranslationArgumentLike argument) { + static @NotNull ComponentLike argument(final @TagPattern @NotNull String name, final @NotNull TranslationArgumentLike argument) { return argument(name, requireNonNull(argument, "argument").asTranslationArgument()); } @@ -97,8 +109,8 @@ public interface NamedTranslationArgument extends TranslationArgumentLike { * @return the named argument * @since 4.19.0 */ - static @NotNull NamedTranslationArgument argument(final @TagPattern @NotNull String name, final @NotNull TranslationArgument argument) { - return new NamedTranslationArgumentImpl(name, argument); + static @NotNull ComponentLike argument(final @TagPattern @NotNull String name, final @NotNull TranslationArgument argument) { + return Component.virtual(Void.class, new NamedTranslationArgumentImpl(name, argument)); } /** @@ -108,4 +120,12 @@ public interface NamedTranslationArgument extends TranslationArgumentLike { * @since 4.19.0 */ @TagPattern @NotNull String name(); + + /** + * The backing translation argument. + * + * @return the translation argument + * @since 4.19.0 + **/ + @NotNull TranslationArgument translationArgument(); } diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java index 5873e4a15..6607cf261 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/translation/NamedTranslationArgumentImpl.java @@ -24,15 +24,18 @@ package net.kyori.adventure.text.minimessage.translation; import java.util.Objects; +import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.TranslationArgument; +import net.kyori.adventure.text.VirtualComponentRenderer; import net.kyori.adventure.text.minimessage.internal.TagInternals; import net.kyori.adventure.text.minimessage.tag.TagPattern; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; -final class NamedTranslationArgumentImpl implements NamedTranslationArgument { +final class NamedTranslationArgumentImpl implements NamedTranslationArgument, VirtualComponentRenderer { private final @TagPattern @NotNull String name; - private final @NotNull TranslationArgument argument; + private final @NotNull TranslationArgument translationArgument; NamedTranslationArgumentImpl(final @TagPattern @NotNull String name, final @NotNull TranslationArgument argument) { Objects.requireNonNull(name, "name"); @@ -40,7 +43,7 @@ final class NamedTranslationArgumentImpl implements NamedTranslationArgument { TagInternals.assertValidTagName(name); this.name = name; - this.argument = argument; + this.translationArgument = argument; } @Override @@ -49,7 +52,17 @@ final class NamedTranslationArgumentImpl implements NamedTranslationArgument { } @Override - public @NotNull TranslationArgument asTranslationArgument() { - return this.argument; + public @NotNull TranslationArgument translationArgument() { + return this.translationArgument; + } + + @Override + public @UnknownNullability ComponentLike apply(final @NotNull Void context) { + return this.translationArgument; + } + + @Override + public @NotNull String fallbackString() { + return ""; // Not for display purposes. } }